Sunday, July 31, 2016

Detecting Web Shells

If a SIEM team during the hunting exercise (or how ever) suspects that a web-shell is present on the monitored web server, the following are some things to examine.

In Splunk, inputs.conf similar to the below and a sourcetype of access_combined / acces_common or access_combined_wcookie (with cookie at the end) can help auto extract the key fields like clientip, clientport, ident, user, req_time, method, uri, root, file, uri_domain, uri_query, version, status, bytes, referer_url, referer_domain, referer_proto, useragent, cookie.

  • The server access and error logs can be searched for common keywords that are being used by web shells. This includes filenames and/or parameter names. The example looks for the string ‘.php’ in URLs in Apache HTTP Server’s access log
  • The filesystem (usually the web server root) must be searched for common strings in files or filenames.
  • Search for very long strings which may indicate encoding. Some backdoors have thousands of lines of code.
  • Search for modified files in the last day/s. In the following example , search for *.php files changed within the last day but it is recommended to search for any file change as a web-shell can also be embedded into an image or any other file.
  • Monitor network for unusual network traffic and connections.
  • Analyze .htaccess files for modifications. Observe the  changes an attacker might make to .htaccess files.

Acunetix has a great, really comprehensive 5 part article about web shells which covers:

Sample generic Splunk searches to hunt for keywords and notable events
Sample generic Splunk searches to hunt for keywords and notable events 
Activity over time with refernence line to indicate the slope or dip in activity
      $index_token$ $sourcetype_token$"$keyword_field$" | timechart count as yvalue | `lineartrend(_time,yvalue)`|timechart sum(yvalue) sum(newY)|rename sum(yvalue) as count|rename sum(newY) as slope<

Supporting Macro
args = x,y
definition = eventstats count as numevents sum($x$) as sumX sum($y$) as sumY sum(eval($x$*$y$)) as sumXY sum(eval($x$*$x$)) as sumX2 sum(eval($y$*$y$)) as sumY2 | eval slope=((numevents*sumXY)-(sumX*sumY))/((numevents*sumX2)-(sumX*sumX)) | eval yintercept= (sumY-(slope*sumX))/numevents | eval newY=(yintercept + (slope*$x$)) | eval R=((numevents*sumXY) - (sumX*sumY))/sqrt(((numevents*sumX2)-(sumX*sumX))* ((numevents*sumY2)-(sumY*sumY))) | eval R2=R*R
iseval = 0

Predict Keyword Occurrences
$index_token$  $sourcetype_token$"$keyword_field$" | timechart count|predict count future_timespan=5
Predict Keyword Recession   
$index_token$  $sourcetype_token$"$keyword_field$" | timechart count|predict count|rename upper95(prediction(count)) as ceiling | rename lower95(prediction(count)) as floor | eval excession=if(count &> ceiling, "100", "0") | eval recession=if(count &< floor, "-100", "0") | table _time,excession,recession,count,ceiling,floor

Cluster command to find anomalies by grouping like events
 $index_token$  $sourcetype_token$"$keyword_field$" | cluster showcount=t t=0.5 | table _time, cluster_count, _raw | sort cluster_count
Reduced events keeping last 5 of each type
 $index_token$ $sourcetype_token$"$keyword_field$" | cluster showcount=t t=0.5  labelonly=t | table _time, cluster_count, _raw | sort cluster_count, - _time<
Outliers: events greater than average count + standard deviation
 $index_token$ $sourcetype_token$"$keyword_field$"|stats count by source |eventstats avg(count) as avg_count stdevp(count) as stdev_count|where count&>(avg_count+stdev_count)
Find Anomalies by Rare Punctuation
     $index_token$  $sourcetype_token$"$keyword_field$" |stats count(punct) as count first(punct) as punctuation first(_raw) as sample by source |sort count |head 10| table count punct source  sample</
Top 10 Punctuations
 $index_token$  $sourcetype_token$"$keyword_field$" |stats count first(punct) as punctuation first(_raw) as sample first(sourcetype) as sourcetype by punct|sort - count|head 10|table count punct sample sourcetype


###### To get the list of Index #####
  <populatingSearch earliest="-4h@h" latest="-5m@m" fieldForLabel="index" fieldForValue="index">| eventcount summarize=false index=*  | stats sum(count) as totalCount by index  | sort - totalCount</populatingSearch>
  <choice value="*">All</choice>
 ###### To get the list of sourcetype #####
 <input type="multiselect" token="sourcetype_token" searchWhenChanged="false">
   <delimiter> OR </delimiter>
   <populatingSearch earliest="-4h@h" latest="-5m@m" fieldForLabel="sourcetype" fieldForValue="sourcetype">| metadata index=* type=sourcetypes  | fields + sourcetype</populatingSearch>
  <choice value="*">All</choice>
###### Search for Keyword #####
  <input type="text" token="keyword_field">
  <label>Enter keyword (e.g. error) to find</label>
 <input type="time" token="field1">