雑記

いち情報系大学生

pico CTFでweb問題を解いてみる #3

picoCTF、web問題の続き。
今回は300point~

Irish-Name-Repo 1

https://play.picoctf.org/practice/challenge/80?category=1&page=1

irishって聞いて、アヤメかなぁ〜と思って指定ページを開いたら外人さんの写真がずらっと出てきた。そういえばアヤメはirisだった。

...と、そんなことはどうでもよくて、今回はアイルランド人(irish)の画像の掲示板みたいなサイトに飛ばされた。サイトの構成としては、

①画像表示ページ
②サポートページ(ユーザからきた質問とadminからの回答が書いてある)
③管理者用ログインページ

の3つで出来ている。問題文に「ログインしてみてくれ」と書いてあったのでとりあえず③に飛んでみた。するとログインフォームが表示されユーザー名とパスワードを求められる。空白にしたり適当な文字列を入力してログインを試みるも、login failedと書かれたところに飛ばされてしまう。

300点問題だしどうせ無駄かなぁと思いつつ、ダメもとで「admin' --」と入力してみるとログインできた。これなら200点問題の「web gauntlet」の方が難しかったぞ...と思った。

picoCTF{s0m3_SQL_c218b685}

Irish-Name-Repo 2

https://play.picoctf.org/practice/challenge/59?category=1&page=1

前問の亜種。50点上がって350点問題。
サイトの構造が全く同じなので、きっとエスケープ処理とかが施されてるんだろうなぁ〜なんて思いつつ、とりあえず今回も「admin' --」を入力してみる。

通ってしまった。なんやねん...

picoCTF{m0R3_SQL_plz_fa983901}

Java Script Kiddie

https://play.picoctf.org/practice/challenge/29?category=1&page=1

問題文には「画像リンクが壊れている」とだけ書かれている。指定されたページに飛ぶと小さい入力フォームだけがあった。

とりあえず適当に文字を入力するとフォームの下に画像が表示された。といっても正しく読み込めていないようで、画像ということを示すアイコンだけが表示されている状態。肝心の画像はわからない。試しに右クリックして画像リンクを取得してみると

data:image/png;base64,V4JOvABUGp2P7/lS+NTvUsNQAc8NBgEAd/NJwU4khWxVAAAOALpEAADeAPMAGK6jfgCF/ImxeQoAAADtST8AZGAUA+A7qxByAAAARQBERJOJs25wSnnuQQEAnACbAF94AOniKE7C+CxUANANKUiKO6RiRwDRAGOwYXjKAIfANmVA/FFHzQrzhR4Wfe0DXVoqSd0ZcvMAdBYEAztLvHep3aG4sgJJSectDmNmmaayzjZ/VPC/3AqjUUDOgIRmxUh/7/1OXQgW78+Sb4/vG/McAK2fxDD3HFRiPzSr1tYa6f5Bam87Sf+Ub2dbFM7eRvzHoXz1vGZRn3euM77zN/Oc+Xx9Ao+/G3eLflgS96vjSEI2+wBQq5JxrQRP09jWendz4S0YNixMK/0F62j4YAjlyEtA6dkXVyj+u2u1yLXptVHnq6VS/sTvMytyqkn5MnLJikALy5vA+eIjvJzfKNlDS2QtXWapDSLFUK/SgInJpy2MUqs41BF+cYvlf9+1DwB03brb5jjpHw/5SneYLCniPCP9rGEgiemlI7VoUNk4us3UD0BR5uaZPvv7L5eNbCAZQQv9d8mT8wsf9+k2ftmIjb/iidWD72SRl5Z3fJ/Lvj8SqtKvet/fcnw7XfWxZA85P++lkA2VIMYnNDVxYVu6TFtKz4XQAPXx9Ul6wd+fUq/xn+fNGFxLC/dNN6oHX3+PYM/yjpni8l2jbrkavASyZp9hNTq6rO8GTtdBnFqWcM1JTJWjn/ItkxDSMf5SfsgePr7mAlartcW5hKqZUr+a65M3OVz8MM92v6r9NX9ej3rm/pqXujeghH45t9mBtV//I98yRk1rZMsRPaMR45O2uE9+7xxzn/5vWvoOzrmJu43n0/H5J2ODX9Iyk/Fff2fvcaXfpPUj54Sm3PHPQ7KUHZxewkrebgDza56t1tL5VEJrKADLiqQA8Qltk89VHcw=

となっていた。
フォームに正しい文字を入力する復号できるといった感じかもしれない。

問題タイトルがJava script kiddyとなっているし、ここで一旦ソースを見てみる。

<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>

早速読んでみる。

最初の数行で行っているのは、https://jupiter.challenges.picoctf.org/problem/17205/bytes というページから文字列を取得して、空白で区切ってから配列に格納するという処理。

お次はassemble_png()関数。ここでは入力キーをもとに複合してるっぽい。if文の中とかを見るとキーは16桁ということもわかる。

さてfor文。まずshifterという変数が登場するが、ここには入力キーのi桁目を文字から数値に変換したものが入る(48という数は'0'の文字コード)。 そして続くfor文の中では、bytes中の要素をshifterをもとにズラしてresultへ格納している。

※ "ズラし方"について説明文を書こうと30分くらい画面と格闘したのですが...文字で書くには複雑すぎてうまくまとめられませんでした。後ほどwriteupを探してみたら、アニメーションでわかりやすく解説されているページがありましたので、よければpicoCTF 2019 - JS Kiddie writeup. Solving two picoCTF web challenges for… | by @radekk | Mediumをご覧ください。

続くwhile文では「resultの最後の要素が0であれば取り除く」というのを繰り返して末尾の連続する0を取り除いており、その後最後に画像のsrcを設定。


以上がコードの中身。
ここからやるべきは正しい16桁のキーを見つけることだが、総当たりで探すには少しばかり数が多すぎる。候補を絞り込むために、pngのファイルシグネチャに着目。pngは先頭16バイトが 137 80 78 71 13 10 26 10 0 0 0 13 73 72 68 82 となるので、bytesの中身から正しい入力キーを逆算できそう。

ともあれ1行になっている状態じゃぁ読みにくいので、簡単なプログラムを書いて16列になるように整列表示

// 雑に書いてます。見辛かったらごめんなさい、
#include <iostream>
#include <vector>
#include <iomanip>
using namespace std;

int main() {
  int byte_num, count16=0;
  vector<int> splited_bytes;

  while(cin >> byte_num){
    splited_bytes.push_back(byte_num);
  } 

  for (auto it = splited_bytes.begin(); it != splited_bytes.end(); ++it){
    count16++ ;
    cout << setw(3) << *it << " ";
    if(count16  == 16){
      count = 0;
      cout << endl;
    }    
  }

}

出力(全45行)↓
f:id:unyamahiro:20201127184537p:plain

これを見るだけで、51081803[a][b][c]63640 まで絞れる。
([a][c]は2or3or4, [b]は3or4or5or6)

このレベルなら総当たりでもいいかなって思って、試すと
51081803[4][5][3]63640 を入力した時に画像が出てきた。

f:id:unyamahiro:20201127190858p:plain

読み込むと picoCTF{066cad9e69c5c7e5d2784185c0feb30b}
これを提出したら無事正解できた。長かった...

余談だけども、htmlの画像指定にbase64が使えることは今回初めて知った。「画像をhtmlに直接埋め込むことでサーバへのHTTPリクエストを削減でき高速化につながる」というメリットもあるらしい(画像が小さい場合に限る)


疲れたのでこの辺で。