还是挺重要的
PHP函数记录
1)php函数md5
ps.
sha1,由于此函数依赖的算法已不足够复杂,不推荐使用此函数对明文密码加密。目前大多用md5但是和md5一样,sha1函数无法处理数组,遇到数组会返回NULL
做题时,了解到了一个新的函数md5
php md5函数介绍为:
md5( string , raw )
string : 规定需要计算的字符串
raw : 规定十六进制或二进制输出格式。
true:16字符二进制格式
false(默认): 32字符十六进制数
比较常用的
数字型:129581926211651571912466741651878684928
字符型:ffifdyop
发现都有’or
‘的形式,可以构造必真的结果
MD5函数有一个漏洞,当输入的为数组时,会返回为NULL
所以当遇到md5(p1)===md5(p2),把p1和p2进行强比较时,又要求p1和p2不相等
由于两个不同的字符MD5值很难一致,于是输入p1[]和p2[]两个名字一致的字符,随便赋值,
p1[]=1&p2[]=2
返回为空,使得强比较成立
为什么会这样呢
我想起之前的遇到一个题,也有数组,问了一下大佬大佬说,因为没有对数组中的元素数量声明,函数不知道数组元素第几个的值是1(或2)
导致函数到处扫,最后返回NULL【因为这个元素位置我们根本就没定】下面举几个例子,以我个人理解
如果是
p1[0]=1&p2[0]=2
是可以的,因为只定义了0号位的数据,而且不相等,其他位置数据没定义,所以比较也不可能出现相等p1[0]=1&p2[1]=1
是可以的,因为这里p1[]定义了0号位为1,而p2定义的是1号位为1,比较是按顺序比较的,两个数值虽然一样,但是位置不一样,所以比较还是不相等
1 | 下列的字符串的MD5值都是0e开头的: |
强类型比较
遇到这种用数组绕过就不行了
(string)$_POST['a'] !== (string)$_POST['b'] && md5($_POST['a']) === md5($_POST['b'])
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 |
2)php函数ereg,null截断
此函数在 PHP 5.3.0中已弃用,并在PHP 7.0.0中删除。
[所以遇到无法执行时,可能是出题人没有注意部署题目环境]
1 | ereg()函数用指定的模式搜索一个字符串中指定的字符串,如果匹配成功返回true,否则,则返回false。搜索字母的字符是大小写敏感的。 |
所以现在一般不再使用ereg
,只是做为preg_match
替代函数使用
3)php函数sleep
当遇到这种需要我们输入参数,但是让参数值必须很大,
而且最后又以这个参数执行sleep函数,就会让我们等很久
1 | sleep((int)$time) |
sleep()
要延缓其程序执行的时间。
但是我们又不能等太久,可以构造php中的科学计数法绕过,就构造一个
time=0.3e7 (等价于0.3乘10的7次方)
这样它的值达到了判断的标准。
而且当它强制转化为整数型(int
)的时候就会因为开头为0.3
是小数变成零,这样可以满足条件。
4)php函数is_numeric()
当遇到类似
0e..;1e;..2e..;...
函数会把其当作科学计数法
这样当遇到
1 | if ($num == 0) { |
这种
第一个要弱类型为
0
第二个要不为
0
才能执行真的判断第三个要求其为数字
就可以用类似0e2
,绕过这三个
5)eval()函数和system()函数的比较
记录一次在打靶机时,因为平常习惯用 eval()函数,但是靶机执行nc等命令时,eval()函数没有反应,就是因为其是代码执行,而非命令执行,所以把两者本质的用法搞懂还是很必要的
eval类型函数是代码执行而不是命令执行(一句话木马)
system类型函数是命令执行而不是代码执行
eval函数里必须是一个符合php语法的语句,如果语句结尾没有分号会报错:eval()’d code
6)PHPのmb系列函数返回值
https://github.com/php/php-src/issues/9008
它会导致奇怪的结果。
1
2
3
4
5
6
7
8
9
10
11
$string = "PHP";
mb_detect_order(["ASCII","UTF-8","BASE64"]);
var_dump(
mb_detect_encoding($string, null, true),
mb_detect_encoding($string, mb_detect_order(), true),
mb_convert_encoding($string, "UTF-8", "BASE64"),
mb_strtolower($string, "BASE64"),
?>得到的结果发现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 Output for 8.2.0
string(5) "ASCII"
string(5) "ASCII"
string(2) "<s"
string(4) "PHM="
Output for 8.0.1 - 8.0.26, 8.1.10 - 8.1.13
string(5) "ASCII"
string(5) "ASCII"
string(2) "<s"
string(4) "PHM="
Output for 8.1.0 - 8.1.9
string(6) "BASE64"
string(5) "ASCII"
string(2) "<s"
string(4) "PHM="
mb_detect_encoding($string, null, true)
返回值只有在
PHP
版本在8.1.0 - 8.1.9
时会返回base64
,而在其他版本都是默认识别为ASCII
mb_detect_encoding()
这类的函数对内容进行编码的识别,就是匹配内容中的一些符合编码的字符,匹配成功对应编码加分
,最后从头到尾匹配完成后,打分最高的编码就被认为是该内容的编码
7)preg_match(‘/^$/‘)
因为preg_match只会去匹配第一行,所以这里可以用多行进行绕过
可以用%0a
绕过,
比如,
1 | preg_match('/^123$/',$a) |
一般情况只有$a
为123
时才可以通过,但是用换行符号%0a
就可以绕过,因为preg_match只匹配一行也就是123
所在的一行,%0a在下一行所以绕过了
8)phpinfo中session
看到session.upload_progress.enabled开启,说明开启session.upload_progress功能,这个功能在我们上传文件时可以把文件上传进度和信息存储在session中。
又看到session.upload_progress.cleanup开启,说明当文件上传结束后,php将会立即清空对应session文件中的内容。所以需要条件竞争。
看到session.save_path,可以看到session文件保存路径。
看到session.use_strict_mode关闭,说明用户可以自己定义自己的sessionid。假如说sessionid=zzzz,则文件上传后会在/tmp目录下生成一个sess_zzzz的文件。
9)strcmp
1 | define('FLAG','pwnhub{this_is_flag}'); |
代码解释
脚本意思是get到的flag和FLAG的值相等,就可以得到FLAG,但我们都不知道flag值是什么,利用strcmp函数特点尝试使用数组绕过。令flag[]=xxx。
strcmp(string $str1,string $str2)
strcmp是比较两个字符串,如果str1<str2 则返回<0 如果str1大于str2返回>0 如果两者相等 返回0。
strcmp比较的是字符串类型,如果强行传入其他类型参数,会出错,出错后返回值0,正是利用这点进行绕过。
flag[]=xxx –> strcmp比较出错 –> 返回null –> null==0 –> 条件成立得到flag
10)无参数 getshell
php7前是不允许用($a)();
这种方法来执行动态函数。php7支持了该方式。
1 | $c = 'phpinfo'; |
去请求
1 | GET: |
如果不让我们括号或者空白字符起手,那这里就涉及到另一种变种了比如phpinfo之前是(~%8F%97%8F%96%91%99%90)
()
,就可以将换成中括号然后空字符换成!%FF
,之后可以变成[~%8F%97%8F%96%91%99%90][!%ff]
,用数组的形式
去绕过,!%ff
表示非,那这里肯定意思就是0了
11)error类|MD5绕过|eval执行
题目[极客大挑战 2020]Greatphp
1 |
|
在这里面,无法用数组进行md5绕过,所以可以用Error类绕过md5和sha1检测
Error类是php的原生类,当md5、sha1对类进行时,会触发类的
__tostring
魔术方法,而Error
类的__tostring
返回值是包含触发代码所处的行数,所以两个变量定义必须在同一行,如下测试$a
和$b
并且,然后由于Error的toString是无法完全控制的,会有其他输出,所以使用
?><?=
结束php从而完整控制整块代码
当然还有其他的原生类也可以用,这边可以用原生类Error或者Exception,只不过 Exception 类适用于PHP 5和7,而
Error 只适用于 PHP 7。
测试
1 |
|
a!=b
md5相等
sha1相等
这道题,
1 | !preg_match("/\<\?php|\(|\)|\"|\'/", $this->syc, $match) |
将<?php
和(
、)
、‘
、“
都过滤了
而<? ?>
相当于对<?php ?>
的替换。而<?= ?>
则是相当于<?php echo ... ?>
于是可以进行替换绕过,
于是第一行大概就有了,必须先闭合原有代码
1 | $str = "?><?= |
然后因为括号以及引号被过滤了,
所以system()和phpinfo()这些都不行,
但是如果直接包含读取文件,也需要引号,
我们直接用url取反绕过即可
1 | $str = "?><?= include ~" . ~"/flag" . "?>"; |
12)escapeshellarg 和 escapeshellcmd
escapeshellarg 和 escapeshellcmd 两个函数在代码的注释里面已经解释了其用法。
也就是说在host变量里面我们不能使用 ; & | 等符号来执行多条命令,不过题目里面提示了我们RCE,同时对于这两个函数简单查找了之后,发现两个一起使用的时候存在漏洞
漏洞解释链接如下:
简单来说
传入的参数是:
172.17.0.2' -v -d a=1
经过
escapeshellarg
处理后变成了'172.17.0.2'\'' -v -d a=1'
,即先对单引号转义,再用单引号将左右两部分括起来从而起到连接的作用。
经过
escapeshellcmd
处理后变成'172.17.0.2'\\'' -v -d a=1\'
,这是因为
escapeshellcmd
对\
以及最后那个不配对儿的引号进行了转义最后执行的命令是
curl '172.17.0.2'\\'' -v -d a=1\'
,由于中间的\被解释为\而不再是转义字符,所以后面的’没有被转义,与再后面的’配对儿成了一个空白连接符。所以可以简化为curl
172.17.0.2\ -v -d a=1’,即向172.17.0.2\发起请求,POST 数据为a=1’。
所以经过我们构造之后,输入的值被分割成为了三部分,第一部分就是curl的IP,为172.17.0.2\ ,第二部分就是两个配对的单引号 ‘ ‘ ,第三部分就是命令参数以及对象 -v -d a=1’
于是我们可以参数绕过这两个过滤函数。
同时,为了构造命令读取flag,我们应当从nmap入手,查资料可以知道,nmap有一个参数-oG可以实现将命令和结果写到文件
所以我们可以控制自己的输入写入文件,这里我们可以写入一句话木马链接,也可以直接命令 cat flag
构造的payload为:
1 | ?host``=``' echo phpinfo(); -oG test.php ' |
13)利用PCRE回溯次数限制绕过某些安全限制
PHP为了防止DOS攻击。给pcre设置了一个回溯上限。默认是100万
PHP < 5.3.7
var_dump(ini_get('pcre.backtrack_limit'))
当待匹配的字符串超过100W,函数就会返回False。也就没未匹配到字符。
那么就可以绕过判断执行恶意代码
1 | import requests |
putenv(‘PATH=/home/rceservice/jail’),jail应用于当前环境,
只允许使用绝对路径执行命令,比如/bin/cat
,而不能用cat
14)basename
basename
可以理解为对传入的参数路径截取最后一段作为返回值,但是该函数发现最后一段为不可见字符时会退取上一层的目录,即:
1 | $var1="/config.php/test" |
15)phar反序列化
1 |
|
读取上传的文件,phar://【上传的文件后的名字并且包括路径】
16)json_decode
1 | $body = file_get_contents('php://input'); |
json解析时的关键字过滤可以采用unicode编码,json是支持用unicode编码直接表示对应字符的,如下两个写法是等价的。
1 | {"poc":"php"} |
至于结果的过滤就好办了,采用php
伪协议的filter
进行下base64编码就好了,最终构造如下payload:
1 | {"page":"\u0070\u0068\u0070://filter/convert.base64-encode/resource=/\u0066\u006c\u0061\u0067"} |
17)取反/异或 绕过无参RCE
与字串相同长度的%ff
异或相当于取反
1 | ${0 b8 ba ab}{ ff}();& ff=phpinfo ff ff ff ff^ a |