【CTF】命令执行RCE
文章目录
一、命令执行几种payload写法
方法一:
查看靶场内容,其中过滤了flag字样,那么也就是说可以执行phpinfo()、system()等的命令。一般目标的敏感文件位于tmp目录下,使用命令c=system(“ls /tmp”)查看tmp目录下的文件,由结果可看出其中有flag.php文件。然后使用命令system(“cat /tmp/flag.php”)即理论上可以查看flag文件,由于该页面过滤了flag字样,那么即可使用通配符的方式也就可以得到题目的flag值。
方法二:
可以在页面代码内部的c执行时,在c中再执行一次eval函数,语句如下:
可以看到,命令内部是有flag的,但是为何没有被过滤呢,这是由于页面代码对c中的flag进行了过滤,但是c的整体仅是一个内部是GET语句的eval语句,而flag并不属于c中的内容,自然也不会被过滤。也就是说传输到命令内,格式为eval(“eval($_GET[w])”),也就是说外层的eval执行的是内部字符串表达式的结果,eval会将内部的整体作为代码执行。
eval功能:返回传入字符串的表达式的结果。
所以,如果将c=eval(…)内部的eval除去后,无法正常显示flag文件,因为没有eval函数后,格式为eval(“($_GET[w])”),eval内部没有任何命令可执行,只是普普通通的接受了一个GET传输入的w的值,而对他没有任何处理。
同理还可以在c后使用system命令。
也还可以使用P7-4节学习的php伪协议,通过语句来获得:
c=include($_GET[w]);&w=php://filter/convert.base64-encode/resource=/tmp/flag.php
通过该语句得到的是flag文件内容的base64编码形式的结果。
方法三:
还可使用命令echo
ls
先查看当前目录下的文件,``反引号是一个操作系统层面的命令,与system函数同级,但是他执行的结果不打印,结果不会回显出来,所以使用了echo命令。
打开靶场后,查看靶场所显示的源代码:
其中if语句内表示,当执行一个GET传入的内容c中没有flag/system/php时,才会执行刚刚输入的c命令。其中preg_match表示做正则匹配的函数。一般目标的敏感文件存放于tmp目录下,tmp目录是linux的临时文件,一般是可读可写的。
那么直接可以使用c=eval($_GET[w]);&w=system(“cat /tmp/flag.php”);即可得到flag。
二、payload命令执行
首先可以查看,页面,可以发现被过滤的字符:
最简单的一种方式,,使用该语句即可得到flag。或者使用
c=include($_GET[w]);&w=php://filter/convert.base64-encode/resource=/tmp/flag.php
也可以得到flag
除了这两种方法,还有什么高阶的方式呢。
由于本题过滤了flag和php,首先使用echo
ls
,并且注意,此处的代码内是过滤了空格的,对于空格的跳过,此处不可以使用$IFS
9
的形式来绕过空格,因为
9的形式来绕过空格,因为
9的形式来绕过空格,因为IFS$9是在操作系统中使用的,但是本题是代码层面的命令,就只能使用%09来绕过空格。首先,为了查看以下本题的tmp目录下的文件,想要使用echo
ls /tmp
命令,即echo%09
ls%09/tmp
,即可发现tmp目录下的flag文件:
但是能够发现,本题的过滤时十分严格的,cat等命令都无法使用。既然cat无法使用,那么尝试命令more/tac等,得到flag:
c=echo%09
tac%09/tmp/f*
;
等价于c=echo
tac /tmp/flag.php
三、命令执行和通配符的绕过
从本题来看,此处过滤的命令更多了,在之前学习的命令都无法使用
查看过滤了的字符,其中%、数字9和空格都被过滤,那么还可以使用${IFS}来绕过空格,然后还可以使用通配符‘?’来绕过对flag的限制。发现cat也被过滤了,那么可以使用单引号绕过的方式:
c=c'a't${IFS}/tmp/fla?.php
即可得到:
注:此处为什么不能够写为f*代表flag.php文件呢,因为通配符中,可以表示一个或多个字符,但是‘?’只能代表一个字符,并且本题的被过滤,无法使用。
还可以使用另一种命令来绕过对cat的过滤,即c=/bin/c?t${IFS}/tmp/fla?.php
也可以:,也能够执行,因为tmp目录下只有flag一个文件
四、php中读文件、命令执行的函数
参考博客:
https://blog.csdn.net/weixin_39687736/article/details/123862218
**1.**system
system(
c
o
m
m
a
n
d
,
command,
command,return)
执行系统命令/php自定义命令,并将相应的执行结果输出,同步进程,执行完后进行后续代码执行。
**2.**exec
exec(
c
o
m
m
a
n
d
,
command,
command,outpub,$return)
exec输出的是命令执行结果的最后一行内容。如果你需要获取未经处理的全部输出数据,请使用passthru()函数。如果想要使用exec显示当前目录下的所有文件的话,可以使用语句:exec(command:“ls >> 1.txt”);来将exec读取到的当前目录下的所有文件名导入到一个新创建的1.txt文件中。
<< : 叠加; < : 覆盖
**3.**passthru
passthru(
c
o
m
m
a
n
d
,
command,
command,return_var)
与system函数同级,若今后题目中system函数被过滤了,即可使用它。
**4.**shell_exec
shell_exec($command)
与exec一样,无回显,需要使用echo显示结果。
5. 反引号 echo
command`
反引号和shell_exec意思相同
在php中称之为执行运算符,PHP 将尝试将反引号中的内容作为 shell 命令来执行,并将其输出信息返回
6. …(见上方连接)
php中读文件函数:
可使用echo fiel_get_contents(“flag.php”)该语句是以流的形式读取文件,需要在查看页面源代码中查看读取到的接果。
hightlight_file(“flag.php”),不需要使用echo
五、intval函数绕过:
函数:
① isset():用于检测变量是否已设置并且非NULL。
② preg_match():正则匹配,前一参数放要搜索的内容,后一参数是需要被匹配的内容,测试被匹配内容中是否存在要搜索的内容。例如本题即num中不能有数字。
③ die():函数输出一条消息,并退出当前脚本。本题中即当num中有数字,输出nonono。
④ intval():用于获取变量的整数值。
intval函数:
语法:
int intval ( mixed $var [, int $base = 10 ] )
参数说明:
$var:要转换成 integer 的数量值。
$base:转化所使用的进制。
如果 base 是 0,通过检测 var 的格式来决定使用的进制:
如果字符串包括了 “0x” (或 “0X”) 的前缀,使用 16 进制 (hex);否则,
如果字符串以 “0” 开始,使用 8 进制(octal);否则,
将使用 10 进制 (decimal)。
返回值
成功时返回 var 的 integer 值,失败时返回 0。 空的 array 返回 0,非空的 array 返回 1。
最大的值取决于操作系统。 32 位系统最大带符号的 integer 范围是 -2147483648 到 2147483647。举例,在这样的系统上, intval(‘1000000000000’) 会返回 2147483647。64 位系统上,最大带符号的 integer 值是 9223372036854775807。
字符串有可能返回 0,虽然取决于字符串最左侧的字符。
在对intval函数的返回值处,发现如果传入空的array(数组),则会返回0,非空的array则会返回1,那么只需要传入一个非空的数组,即可实现让本程序输出flag值。
要如何传入一个数组呢,格式:num[]=w,即在需要传入的num后加上方括号,即代表此时传入的是一个数组,并且使传入的数组为非空且无数字,即可得到flag。
并且,本题如果直接书写为num[]=或者num[]也可以得到flag,因为如果不定义num的值的话,默认num有一个值为空,也满足条件。
例题:
得到代码后,分析代码:
分析本题,当num为4476时,程序会die,并输出nonono,但是想要获得flag的话,又需要num为4476。
那么这里就涉及到intval函数的一个知识点,当int intval ( mixed $var [, int $base = 10 ] )中的base为0时,通过检测 var 的格式来决定使用的进制,
如果字符串包括了 “0x” (或 “0X”) 的前缀,使用 16 进制 (hex);否则,
如果字符串以 “0” 开始,使用 8 进制(octal);否则,
将使用 10 进制 (decimal)。
那么推测,是否可以通过输入16进制或者8进制的num值,来绕过本程序的检验,先进行进制转换
4476的16进制:0x117c,8进制:010574
尝试将上述两种进制以格式传入,
十六进制:num=0x117c
八进制:num=010574
使用以上两种方式,都成功的获取到了本题的flag。
除了以上的方法,由于intval会取整,可以使用小数的方式来得到,例如输入4476.1、4476w等方式,都能够得到flag。
六、php弱类型特性
**属于弱类型校验,只校验值是否一样,不校验类型,而=**属于强类型校验,不仅校验值,还校验类型。
本题及表示,当a与b同时都有POST输入,而a与b的传输不相同,但是a和b的md5值相同,才能够得到flag,否则输出wrong。
方法一:
md5()函数:
语法:
md5(string,raw)
定义和用法:
md5() 函数计算字符串的 MD5 散列。
md5() 函数使用 RSA 数据安全,包括 MD5 报文摘要算法。
来自 RFC 1321 的解释 - MD5 报文摘要算法:MD5 报文摘要算法将任意长度的信息作为输入值,并将其换算成一个 128 位长度的"指纹信息"或"报文摘要"值来代表这个输入值,并以换算后的值作为结果。MD5 算法主要是为数字签名应用程序而设计的;在这个数字签名应用程序中,较大的文件将在加密(这里的加密过程是通过在一个密码系统下[如:RSA]的公开密钥下设置私有密钥而完成的)之前以一种安全的方式进行压缩。
如需计算文件的 MD5 散列,请使用 md5_file() 函数。
md5() 函数不能处理数组,数组都返回 null,md5(a[]) 结果为 null。
本题运用到了md5()函数的不能处理数组,数组都返回 null,md5(a[]) 结果为 null的特性,当给a和b传入不同两个数组时,在第一步判断时不会认定a与b相同,但是在第二步判断时,由于md5函数不能处理数组,所以会返回null那么即可传输:a[]=1&b[]=2,即可得到flag:
方法二:
PHP在处理哈希字符串时,会利用”!=”或”==”来对哈希值进行比较,它把每个以”0E”开头的哈希值都解释为0,因此若是两个不一样的密码通过哈希之后,其哈希值都是以”0E”开头的,那么PHP将会认为他们相同,都是0。
因为php中0e代表0的科学计数法,无论后面的数字跟的是多少,结果都等于0。
http://www.javashuo.com/article/p-adowhkuk-q.html
然后在本题中,利用一些经过md5加密后开头为0e的原始值,来对a和b进行赋值,即可成功绕过:
a=s878926199a&b=s155964671a
七、变量覆盖
函数:
定义和用法
parse_str() 函数把查询字符串解析到变量中。
注释:如果未设置 array 参数,由该函数设置的变量将覆盖已存在的同名变量。
注释:php.ini 文件中的 magic_quotes_gpc 设置影响该函数的输出。如果已启用,那么 在 parse_str() 解析之前,变量会被 addslashes() 转换。
语法
parse_str(string,array)
参数****描述string必需。规定要解析的字符串。array可选。规定存储变量的数组名称。该参数指示变量存储到数组中。
方法一****:
结合parse_str()函数的性质,本题需要传入两个值,一个是post传输的v1和一个GET传输的v3,也就是说本题需要传入一个v1=‘flag’=(v3的md5加密后的值)。
接下来执行后即可获得本题的flag。
方法二:
运用md5()函数对数组进行操作后结果为null的性质,对v3当作数组进行传入值,然后不对v1传入任何值也可获得本题的flag。
$$变量覆盖
foreach有两种语法:
第一种:遍历给定的 数组语句 array_expression 数组。每次循环中,当前单元的值被赋给 $value 并且数组内部的指针向前移一步(因此下一次循环中将会得到下一个单元)。
foreach (array_expression as $value)
第二种:同上,同时当前单元的键名也会在每次循环中被赋给变量 $key。
foreach (array_expression as $key => $value)
本题的foreach函数里有GET和POST两个参数,那么也就是证明,可以自定义的通过GET/POST的方式传输进几个变量,并通过键值对的形式赋值。
k
e
y
=
key=
key=value****:
当传入一个a=1时,此时变为了$a=
1
,也就是把
a
变换为了变量
1,也就是把a变换为了变量
1,也就是把a变换为了变量a,把1变为了变量$1,并把
1
赋值给
1赋值给
1赋值给a。这与$
k
e
y
=
key=
key=value有何区别呢,当value传入一个值后,其由于$value本身就是一个变量,他就会表示一个具体的变量的值。
那么对于本题而言,出现了两次
k
e
y
=
key=
key=value,再结合下方的if语句:
只要阻止这一步执行了die命令,就可以获得flag,但是想要使传入的flag等于题目的flag显然是不现实的,也就是说在最后一个if语句处,一定会die,但是可以通过变量相等的传递,来逐步实现。比如对于本题,在第一个foreach处先传入一个suces=flag,然后在第二个foreach处通过error=suces的形式,即可实现对flag和error两个字符串的过滤(因为他们变成变量的形式了)。在最后一个if语句,执行了die(
e
r
r
o
r
)
,但是此时的
e
r
r
o
r
已经等于了
error),但是此时的error已经等于了
error),但是此时的error已经等于了flag,也就是说,输出的会是flag。
那么经过两次传参,也就是说
第一次:
s
u
c
e
s
=
suces=
suces=flag
第二次:
e
r
r
o
r
=
error=
error=suces
也就是说,此时的
e
r
r
o
r
在值上等于
error在值上等于
error在值上等于flag,那么即可通过die($error)语句,获得flag。
版权归原作者 Sunny-Dog 所有, 如有侵权,请联系我们删除。