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

hamayanhamayan's blog

Construct Good Path [AtCoder Beginner Contest 244 G]

https://atcoder.jp/contests/abc244/tasks/abc244_g

解説

https://atcoder.jp/contests/abc244/submissions/30323833

何から始めればいいか困る問題。
構築問題で重要なのはなるべくシンプルなルールで構築を考えていくことである。
今回は特に最適なパス長である必要はないので、若干非効率な方法でも許される。
効率よりも分かりやすさというかシンプルな方法を模索しよう。

どんな条件でも作れる

今回の問題設定では、条件を満たすどんなグラフとSの組であっても良いパスを作ることができるらしい。
ということは与えられたグラフから辺をいくつか削除して、木にしたとしても答えが得られることになる。
この工夫は試行錯誤の結果ではあるのだが、グラフを木にすることでちょっとだけ問題をシンプルにしておこう。

根付き木として考えてみる

スタート地点を1つ決めてそこを根として木を見てみよう。
移動していくと、葉まで行って偶奇が異なる場合はその親に一回戻って葉に再度移れば、偶奇を変更することができる。
これは葉に限らず、他の頂点でも同様である。
つまり、何が言いたいかというと

「現在いる頂点の偶奇を変えたい場合は、親に移動してから自分に戻ってくれば、
 『自分の子孫の偶奇を変更することなく』自分の偶奇を変更可能」

ということである。これは非常に都合がいい。
よって、木をdfsで探索しながら子供の偶奇を全部一致させた後、自分の偶奇を以上のテクで一致させれば、
自分を含めた部分木の偶奇を全部一致させた上で親に戻ることができる。

ここら辺の認識が一番難しい。
実装に落とし込むと割と完結になっているので、実装から思想を吸い上げてもいいかもしれない。

つまり、実装は?

適当に頂点0を始点としてdfsを行う。
訪れた頂点をメモして再度訪れないようにしておけば、無向グラフであっても木上でdfsをしているようなコードが書ける。
各頂点でやることとしては、

  1. 子頂点についてdfsを再度行って、子頂点について偶奇が一致するようにする
  2. 子頂点について偶奇が一致したら、親を使って自分の偶奇を一致させる

を繰り返す。
注意点として頂点0は親を持っていないので偶奇が調整できない。
これは、数列として0→1→2→1→0みたいな数列を作っていると思うが、
最初の0を取り除けばこれまでの偶奇は変わらずに頂点0の偶奇だけを変更できる。

4Nにおさまってる?

正直、未証明ACだが、調整は各頂点で1回ずつしか発生しないし、普通に回るのに2Nかかって調整で2Nかかってで
間に合いそうな雰囲気はある。
(コンテスト中に必ず通すという思想では、始点を0ではなくランダムに設定すると嫌味なケースを回避できるかも)

自分の実装では行ってみて偶奇が合わないなら調整する形をとっているが、そもそも行かなくていいという
ケースを処理すればもっと改善できそう。

int N, M;
vector<int> E[101010];
string S;
//---------------------------------------------------------------------------------------------------
deque<int> ans;
int parity[101010];
void add(int cu) {
    parity[cu] ^= 1;
    ans.push_back(cu);
}
//---------------------------------------------------------------------------------------------------
bool vis[101010];
void dfs(int cu, int pa = -1) {
    vis[cu] = true;

    add(cu);
    fore(to, E[cu]) if(!vis[to]) {
        dfs(to, cu);
        add(cu);
    }

    if (0 <= pa && parity[cu] != S[cu]) {
        add(pa);
        add(cu);
    }
}
//---------------------------------------------------------------------------------------------------
void _main() {
    cin >> N >> M;
    rep(i, 0, M) {
        int u, v;
        cin >> u >> v;
        u--; v--;
        E[u].push_back(v);
        E[v].push_back(u);
    }
    cin >> S;

    fore(c, S) c -= '0';

    dfs(0);
    if (parity[0] != S[0]) ans.pop_front();

    int K = ans.size();
    printf("%d\n", K);
    while (!ans.empty()) {
        if (ans.size() != K) printf(" ");
        printf("%d", ans.front() + 1);
        ans.pop_front();
    }
    printf("\n");
}

Shortest Good Path [AtCoder Beginner Contest 244 F]

https://atcoder.jp/contests/abc244/tasks/abc244_f

前提知識

解説

https://atcoder.jp/contests/abc244/submissions/30322530

今回の問題はbitmaskを状態として利用したbfsで解くことができる。
状態の持ち方としてbitmaskを利用するという方法はbit全列挙やbitDPでも見られる。
bitmaskを利用する方法については説明しないので、少なくともbit全列挙は理解した上で読んでほしい。

何から始めるか

解法を見れば、なるほどと思う人も多いかもしれない。
複雑な問題設定なので、すこし面食らってしまった人もいるかもしれない。

この問題の特徴としてはN≦17である。
Sとして考えられる文字列は全列挙できるので、それぞれに最短の長さを求めて総和をそのまま取ればよさそう。
逆に言えば、Sとして考えられる文字列のそれぞれについて最短距離を求める必要がある。
なので、以下を定義して考察を始めてみよう。

opt[S] := 文字列Sに関する良いパスのうち最短のものの長さ

最短?

パスが伸びていく過程を考えていくと、パスの長さというのは1つずつしか増えない。
+1ずつであり最短距離と言えばbfsである。
bfsで最短距離を計算していく上で、opt[S] -> opt[next_S]を計算したいという前提に立ってみると、
パスの終端は状態に必要になってくる。
よって、保持する状態を変更しておこう。

opt[S][lst] := 文字列Sに関する良いパス、かつ、最後がlstのパスのうち最短のものの長さ

これでopt[S][lst]からの遷移はopt[next_S][to]と遷移が行えるようになった。
これでbfsをしていけば求めたい最短長が求められる。

msk=0の時がちょっと厄介なので、最初の遷移は別途やってしまうことにして、後はbfsをするだけ。
計算量は分かりにくいがO(2N(N+M))のはず。

int N, M;
vector<int> E[17];
int opt[1 << 17][17];
//---------------------------------------------------------------------------------------------------
void _main() {
    cin >> N >> M;
    rep(i, 0, M) {
        int u, v;
        cin >> u >> v;
        u--; v--;
        E[u].push_back(v);
        E[v].push_back(u);
    }

    queue<pair<int, int>> que;
    rep(i, 0, N) {
        opt[1 << i][i] = 1;
        que.push({ 1 << i, i });
    }

    while (!que.empty()) {
        int msk, lst;
        tie(msk, lst) = que.front();
        que.pop();

        fore(to, E[lst]) {
            int msk2 = msk ^ (1 << to);
            if (opt[msk2][to] == 0) {
                opt[msk2][to] = opt[msk][lst] + 1;
                que.push({ msk2, to });
            }
        }
    }

    int ans = 0;
    rep(msk, 1, 1 << N) {
        int mi = inf;
        rep(lst, 0, N) chmin(mi, opt[msk][lst]);
        ans += mi;
    }
    cout << ans << endl;
}

King Bombee [AtCoder Beginner Contest 244 E]

https://atcoder.jp/contests/abc244/tasks/abc244_e

前提知識

解説

https://atcoder.jp/contests/abc244/submissions/30321733

まず前提としてDPが分かっていないとこの問題を解くことは難しい。
DPが未履修であれば、先に学習してこよう。
DPの入門問題としては向かない。

DP?

mod数え上げは意外と選択肢が無いし、数列の数え上げなのでDP感がすごい。
DPに乗せられるように情報を圧縮していくことを考えよう。

最初は
dp[i] := 数列A[i]まで確定している数列の組み合わせ
からスタート。

数列のA[i]まで確定しているときに次のA[i+1]を決めるには最後の要素が何かだけ分かっていればいい。
なので、状態として最後の要素を含める必要がある。
dp[i][lst] := 数列A[i]まで確定していて、最後の要素がlstである数列の組み合わせ

だが、これでは整数Xの出現回数が分からないので答えを導くことができない。
なので、整数Xの出現回数を含めるのだが、
dp[i][lst][cnt] := 数列A[i]まで確定していて、最後の要素がlstであり、Xがcnt回出現した数列の組み合わせ
としてしまうと状態数がO(KN2)となりちょっとメモリに載らない。
載らないし、計算量もアレなので、問題に即して偶数回出現しているかを保持することにしよう。

dp[i][lst][mod2] :=
数列A[i]まで確定していて、
最後の要素がlstであり、
Xが出現した回数を2で割ったあまりがmod2である
数列の組み合わせ

これがDPの定義になる。
あとはこれを実装する。

実装の注意点

状態のループで既にO(NK)なのであまり無茶はできない。
普通の更新の感じで
dp[i][lst][mod2]からdp[i+1][to][next_mod2]みたいな感じにtoを全探索するとO(N2 K)となって間に合わない。
lst -> toへは辺にあるものだけ実施すればいいのでtoを全探索するのではなく、lstから辺があるtoを全探索しよう。
すると、計算量はO(K(N+M))となって間に合う。

補足:O(K(N+M))??

N+Mの部分が良く分からないと思うのだが、ならし計算量が混ざっている。
例えば rep(lst, 0, N) rep(to, 0, N)とするとこれはO(N2)である。
だが、rep(lst, 0, N) foreach(to, E[lst])とtoをlstから遷移可能なものにforeachで列挙するとO(N+M)となる。
O(N+M)ということはO(N)とO(M)で大きいほうが最終的な(支配的な?専門的な言い方が分からない)計算量となる。

rep(lst, 0, N) foreach(to, E[lst]) { // ここの計算 }のコメント部分が何回評価されるだろうかというのを
考えると、辺があると評価されるので2M回評価されることになる。
「ならし」で2M回評価されるので、処理全体で見るとO(M)ではあるのだが、foreachの評価回数を見るとN回なので、
ループ的にはO(N)で、ネスト内部の評価回数はO(M)となるので、O(N+M)

int N, M, K, S, T, X;
vector<int> E[2010];
mint dp[2010][2010][2];
//---------------------------------------------------------------------------------------------------
void _main() {
    cin >> N >> M >> K >> S >> T >> X;
    S--; T--; X--;

    rep(i, 0, M) {
        int u, v;
        cin >> u >> v;
        u--; v--;
        E[u].push_back(v);
        E[v].push_back(u);
    }

    dp[0][S][0] = 1;
    rep(i, 0, K) rep(lst, 0, N) rep(mod2, 0, 2) {
        fore(to, E[lst]) {
            dp[i + 1][to][mod2 ^ (to == X)] += dp[i][lst][mod2];
        }
    }

    cout << dp[K][T][0] << endl;
}

UMDCTF 2022 Forensicまとめ

CTFtime.org / UMDCTF 2022
久々に出たので、備忘録レベルで残しておく。

Renzik's Case

USBの論理イメージが与えられるので、削除された重要なデータを探す問題。
自分はAutopsyを利用して論理イメージを読み込んで、削除されたデータを探索した。

Xorua

ちょっとした思考パズルを要求される。
Before.png, After.pngというファイルが与えられるのでバイナリエディタで開いて観察してみよう。
以下のことより、Before.pngに対してフラグファイルをxorするとAfter.pngになると予想した。

  • 同じファイルサイズである
  • After.pngの先頭が不自然に0x00になっている
  • 問題名がXorua

「Before.png xor flag = After.png」であるが、XORの性質を利用すると「flag = Before.png xor After.png」であるので、xor結合するpythonコードを適当に書いて実行。

import struct

b1 = open('Before.png', 'rb').read()
b2 = open('After.png', 'rb').read()

b3 = b''
for i in range(len(b1)):
    b3 += struct.pack("B", b1[i] ^ b2[i])

f = open('flag.png', 'wb')
f.write(b3)

運よく予想が当たり、フラグが得られる。

How to Breakdance

USB通信が入ったpcapファイルが与えられる。
通信からキータイプを抜き出す問題だろう。以下のページを参考に抜き出す。
Decoding Mixed Case USB Keystrokes from PCAP

wiresharkのクエリ的には((usb.transfer_type == 0x01) && (frame.len == 72)) && !(usb.capdata == 00:00:00:00:00:00:00:00)で検索して、Leftover Capture Dataを持ってきて、上のページにあるスクリプトに食わせてやればいい。

Blue

ステガノグラフィー解析の問題。画像に情報を埋め込むためのコードと、埋め込まれた後の画像が与えられる。
画像の高さは775であり、こんなに文字長が大きくないだろうので、埋め込まれた後の画像の後半は元々の色になっているはずである。
なので、画像の一番下のピクセルからもともとの色、RGB値を読み込んで、解析に利用する。
埋め込みコードでは、ランダムなx座標とRGBのいずれかに+1をしながら文字を埋め込んでいるので、基準のRGB値からの差をすべてのx座標から計算して総和を取ることで復元する。
これより、以下のコードで復元可能。

from PIL import Image

orig_image = Image.open('bluer.png')
pixels = orig_image.load()
width, height = orig_image.size
orig_pixel = list(orig_image.getpixel((0, height - 1)))

ans = ''
for y in range(height):
    cnt = 0
    for x in range(height):
        pixel = list(orig_image.getpixel((x, y)))
        for c in range(3):
            cnt += pixel[c] - orig_pixel[c]
    if cnt == 0:
        break
    ans += chr(cnt)

print(ans)

Magic Plagueis the Wise

見ると先頭1ビットだけ違っていて、他は全部一緒のファイル群。
先頭だけ抜き出して眺めてみると、Asciiっぽいので、文字起こしするとフラグが書いてあった。

import glob

files = glob.glob("./magic_plagueis_the_wise/*")
l = len(files)
tops = list(range(l))
for file in files:
    name = int(file.split('\\')[-1])
    print(name)
    with open(file, 'rb') as f:
        tops[name - 1] = f.read()[0]

ans = ''
for c in tops:
    ans += chr(c)
print(ans)

jdata 解けなかった

zipファイルが与えられる。解凍するとpngファイルがあり、フラグの末尾が書いてあるが、前半部分が見つからない。
んー、分からない。
0x52C50付近に怪しいものがあったが、繋がらないな…

UMDCTF(writeup) - HHJ :: Import HHJ

解説を見ると、zipはelfファイルらしい。
解凍時にエラー出てたけど無視してたな…いかん。
elfファイルをデコンパイルして中身を見てみるとフラグの前半が書いてある。

Class Project 解けなかった

起動しなくなったVMイメージが与えられるのでトラブルシューティングしよう。
適当にlinuxイメージをUSBで接続して中身を見ていくと、/home/aman_esc/Documents/fork_bomb.bashというのがある。
見慣れぬコマンドだったが、調べてみるとFork爆弾というもので、DoSの一種らしい。
linux - What this command :(){ :|: & };: does? - Super User
これが原因で起動しないのか。削除すると起動するようになる。

んー、その後が分からんな…

CTF-Writeups/UMDCTF_2022/Class Project at main · K1nd4SUS/CTF-Writeups · GitHub

/home/aman_esc/Documents/admin_notesbase64デコードされたフラグが書いてあったららしい。
このファイル見つけたのにデコードしなかったな。なんでかな。

Kernel Infernal 1

カーネルのクラッシュログが与えられる。
第674回 カーネルのクラッシュ情報を解析する:Ubuntu Weekly Recipe|gihyo.jp … 技術評論社
この辺を参考にしながら、crashコマンドで中身を見てみる。

適当にいろんなサイトを巡回しながらやってると、filesで怪しいものが出てくる。

crash> files
PID: 5206   TASK: ffff9b1878e45d00  CPU: 1   COMMAND: "bash"
ROOT: /    CWD: /home/ubuntu/XXXXXXXXXXXXXXXXXXXXXX1SCEhfQ==
 FD       FILE            DENTRY           INODE       TYPE PATH
  0 ffff9b1878f87500 ffff9b1852988180 ffff9b18529a2720 CHR  /dev/pts/1
  1 ffff9b1878f99a00 ffff9b187c093540 ffff9b187c1cfad8 REG  /proc/sysrq-trigger
  2 ffff9b1878f87500 ffff9b1852988180 ffff9b18529a2720 CHR  /dev/pts/1
 10 ffff9b1878f87500 ffff9b1852988180 ffff9b18529a2720 CHR  /dev/pts/1
255 ffff9b1878f87500 ffff9b1852988180 ffff9b18529a2720 CHR  /dev/pts/1

CWDに書いてあるパスに含まれる文字列をbase64デコードするとフラグが得られる。

Subtree K-th Max [デンソークリエイトプログラミングコンテスト2022(AtCoder Beginner Contest 239) E]

https://atcoder.jp/contests/abc239/tasks/abc239_e

前提知識

解説

https://atcoder.jp/contests/abc239/submissions/29488895

データ構造でゴリ押して解けそうな感じもするし、Kの値が小さいことを利用して、楽に解くこともできそうではある。
根付き木の部分木に対する操作ではオイラーツアーが有効な場面が多々ある。

部分木に対する操作を配列の特定区間に対する操作に変換する

オイラーツアーを使うと部分木を配列の特定区間マッピングすることができる。
初めて聞く場合は何を言っているのか分からないと思うが、かなり便利なので検索して学習してこよう。
オイラーツアー - Google 検索
(ちょっと、ここに書くには余白が少なすぎる…)

オイラーツアーを使って部分木からインデックスを作成して、頂点Vに対する部分木に対応する区間が[L,R)に対応すると
分かったとする。すると、今回の問題を以下のように読み替えることができる。

頂点Vに対する部分木に含まれる数のうちK番目に大きい数は?

配列の区間[L, R)に含まれる数のうちK番目に大きい数は?

これで木であるということは無視してよくなるので、だいぶ今までの知見を活かしやすくなった。

特定区間のK番目に大きい数は?

これはWaveletMatrixというデータ構造を使用すると高速に取得できる。
しかし、結構高度なデータ構造なので、今回は、Kが小さいことを利用してセグメントツリーで解くことにする。
セグメントツリーを使えば最も大きい数というのは取得が可能である。
今回の問題ではK≦20と小さいので、

最も大きい数を取得する
→ 一旦それを除外する(正確には除外というか-1で更新する)
→ 最も大きい数を取得する(2番目に大きい数が得られる)
→ 一旦それも除外する
→ 最も大きい数を取得する(3番目に大きい数が得られる)

みたいなことを繰り返してK番目に大きい数を取得することにする。
取得した後は、除外した項目をセグメントツリーに戻してやることで取得操作の過程で壊れてしまった
セグメントツリーを復元していく。
こうすると、全体的な計算量はO(NKlogN)となり、K≦20,N≦105で2secだとちょっと不安な気もするが、
操作も比較的単純で定数倍も小さそうということもあり、まあ通るかという感じ。

advancedな話

  • wavelet matrixをやればストレートに区間問題パートが解けるので勉強してみるのも面白い
  • 自分の解法では消してundoみたいなことをしているが深堀したい場合は「永続化セグメントツリー」のような永続データ構造を調べてみても面白い
int N, Q, X[101010];
vector<int> E[101010];
int V[101010], K[101010];

int L[101010], R[101010];
int idx = 0;
void euler(int cu, int pa = -1) { // [L[v],R[v])
    L[cu] = idx; idx++;
    for (int to : E[cu]) if (to != pa) euler(to, cu);
    R[cu] = idx;
}

SegTree<1<<17> st;
void _main() {
    cin >> N >> Q;
    rep(i, 0, N) cin >> X[i];
    rep(i, 0, N - 1) {
        int a, b; cin >> a >> b;
        a--; b--;
        E[a].push_back(b);
        E[b].push_back(a);
    }
    rep(i, 0, Q) cin >> V[i] >> K[i], V[i]--;

    euler(0);

    rep(i, 0, N) st.update(L[i], { X[i], L[i] });
    
    rep(q, 0, Q) {
        int l = L[V[q]], r = R[V[q]];
        vector<pair<int, int>> buf;
        rep(k, 0, K[q] - 1) {
            auto p = st.get(l, r);
            buf.push_back(p);
            st.update(p.second, { -1, -1 });
        }

        auto p = st.get(l, r);
        printf("%d\n", p.first);
        fore(p, buf) st.update(p.second, p);
    }
}

Subtree K-th Max [デンソークリエイトプログラミングコンテスト2022(AtCoder Beginner Contest 239) E]

https://atcoder.jp/contests/abc239/tasks/abc239_e

前提知識

解説

https://atcoder.jp/contests/abc239/submissions/29488895

データ構造でゴリ押して解けそうな感じもするし、Kの値が小さいことを利用して、楽に解くこともできそうではある。
根付き木の部分木に対する操作ではオイラーツアーが有効な場面が多々ある。

部分木に対する操作を配列の特定区間に対する操作に変換する

オイラーツアーを使うと部分木を配列の特定区間マッピングすることができる。
初めて聞く場合は何を言っているのか分からないと思うが、かなり便利なので検索して学習してこよう。
オイラーツアー - Google 検索
(ちょっと、ここに書くには余白が少なすぎる…)

オイラーツアーを使って部分木からインデックスを作成して、頂点Vに対する部分木に対応する区間が[L,R)に対応すると
分かったとする。すると、今回の問題を以下のように読み替えることができる。

頂点Vに対する部分木に含まれる数のうちK番目に大きい数は?

配列の区間[L, R)に含まれる数のうちK番目に大きい数は?

これで木であるということは無視してよくなるので、だいぶ今までの知見を活かしやすくなった。

特定区間のK番目に大きい数は?

これはWaveletMatrixというデータ構造を使用すると高速に取得できる。
しかし、結構高度なデータ構造なので、今回は、Kが小さいことを利用してセグメントツリーで解くことにする。
セグメントツリーを使えば最も大きい数というのは取得が可能である。
今回の問題ではK≦20と小さいので、

最も大きい数を取得する
→ 一旦それを除外する(正確には除外というか-1で更新する)
→ 最も大きい数を取得する(2番目に大きい数が得られる)
→ 一旦それも除外する
→ 最も大きい数を取得する(3番目に大きい数が得られる)

みたいなことを繰り返してK番目に大きい数を取得することにする。
取得した後は、除外した項目をセグメントツリーに戻してやることで取得操作の過程で壊れてしまった
セグメントツリーを復元していく。
こうすると、全体的な計算量はO(NKlogN)となり、K≦20,N≦105で2secだとちょっと不安な気もするが、
操作も比較的単純で定数倍も小さそうということもあり、まあ通るかという感じ。

advancedな話

  • wavelet matrixをやればストレートに区間問題パートが解けるので勉強してみるのも面白い
  • 自分の解法では消してundoみたいなことをしているが深堀したい場合は「永続化セグメントツリー」のような永続データ構造を調べてみても面白い
int N, Q, X[101010];
vector<int> E[101010];
int V[101010], K[101010];

int L[101010], R[101010];
int idx = 0;
void euler(int cu, int pa = -1) { // [L[v],R[v])
    L[cu] = idx; idx++;
    for (int to : E[cu]) if (to != pa) euler(to, cu);
    R[cu] = idx;
}

SegTree<1<<17> st;
void _main() {
    cin >> N >> Q;
    rep(i, 0, N) cin >> X[i];
    rep(i, 0, N - 1) {
        int a, b; cin >> a >> b;
        a--; b--;
        E[a].push_back(b);
        E[b].push_back(a);
    }
    rep(i, 0, Q) cin >> V[i] >> K[i], V[i]--;

    euler(0);

    rep(i, 0, N) st.update(L[i], { X[i], L[i] });
    
    rep(q, 0, Q) {
        int l = L[V[q]], r = R[V[q]];
        vector<pair<int, int>> buf;
        rep(k, 0, K[q] - 1) {
            auto p = st.get(l, r);
            buf.push_back(p);
            st.update(p.second, { -1, -1 });
        }

        auto p = st.get(l, r);
        printf("%d\n", p.first);
        fore(p, buf) st.update(p.second, p);
    }
}

CTFのWebセキュリティにおける細かな話題まとめ(Unicode, Reguler Expression, LDAP Injection, Dependency Confusion, DoS)

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

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

脆弱性を利用する

  • 問題によっては脆弱性を突いた問題がある
    • 時事ネタが多い気がする。ゼロデイやワンデイという感じ
  • ソースコードが配布されていて、バージョンが固定されたライブラリを使っているなら怪しい

Dependency Confusion

DoS

  • セキュリティ的にも問題とされる。情報は抜かれないが、事業停止に追いやることができる。CTFではあまりテーマとして出題されない。
    • でも解法でDoSが思いついても、解法にかなり確信度が無い限りやらないことを勧める(もしかしたらサーバ壊すかも)
  • 何がDoSの原因となるか?
    • 再帰的な構造があるもの
    • 指数関数的に伸びる可能性があるもの
      • jsonのparseとか(深さが深いとバッファが食われる)
    • 受け付けるサイズの上限がない場合
    • 格安ホスティングにより、ほかの人がDoSを受けると影響を受ける
    • エラーを起こすような入力を与えることで、StoredXSSの要領でそのページを開くときに毎回エラーを発生させて、画面表示させないようにできる
  • 資料

Git系

SVGへの攻撃方法

ImageMagick

GraphicsMagick

SVG Writeups

Exif

LDAP

Reflected File Download

正規表現

ReDoS

Blind Regular Expression Injection Attack

聖典A Rough Idea of Blind Regular Expression Injection Attack – やっていく気持ち
聖典から聖典が生まれていた → Revisiting ReDoS: A Rough Idea of Data Exfiltration by ReDoS and Side-channel Techniques - Speaker Deck

  • これは何?
    • ReDoSによるレスポンス遅延をオラクルとしたサイドチャネル攻撃
    • 以下、想像(ちゃんと読んでない)
    • 正規表現にマッチしないと全探索をする必要があって、遅くなる
    • なので、「正規表現にマッチ→正解、マッチしない不正解」となるように正規表現を組み、応答時間を測ることで、情報を1文字ずつ抜き取る
  • 問題

Unicode

XPath Injection