Giter Site home page Giter Site logo

fox-it / cisco-ios-xe-implant-detection Goto Github PK

View Code? Open in Web Editor NEW
37.0 8.0 8.0 133 KB

Cisco IOS XE implant scanning & detection (CVE-2023-20198, CVE-2023-20273)

Home Page: https://twitter.com/foxit/status/1716472673876730149

License: Apache License 2.0

Python 100.00%
cisco cisco-ios-xe cve-2023-20198 cve-2023-20273 iocisco pcap suricata badcandy

cisco-ios-xe-implant-detection's Introduction

Cisco IOS XE implant scanning & network detection

Network detection of CVE-2023-20198 exploitation and fingerprinting of post-exploitation of Cisco IOS XE devices.

CVE-2023-20198 Suricata network detection

The suricata/ folder contains Suricata detection rules for exploitation of CVE-2023-20198. These rules monitor for a percent-encoded-percent which can be used to bypass authentication on Cisco IOS XE devices not patched for CVE-2023-20198.

This directory also contains reference PCAPs based on observed in-the-wild exploitation traffic:

Cisco IOS XE implant scanning

This repository also contains information regarding post-exploitation activities linked to the Cisco IOS XE Software Web Management User Interface mass exploitations. Cisco Talos 1 published a fingerprint that could check if the implant was active on Cisco IOS XE devices. For reference:

curl -k -X POST "https://DEVICEIP/webui/logoutconfirm.html?logon_hash=1"

If the HTTP response consists of a hexadecimal string, this is a high-confidence indicator that the device is compromised. However, as multiple sources have mentioned 2 3, the number of implants that can be discovered using this method has gone down significantly.

Upgraded Implant

Investigated network traffic to a compromised device has shown that the threat actor has upgraded the implant to do an extra header check. Thus, for a lot of devices, the implant is still active, but now only responds if the correct Authorization HTTP header is set.

Alternate method for Cisco IOS XE implant scanning

We took another look at the initial blogpost by Cisco Talos and noticed an extra location check in the implant code:

implant-location-percent

Based on the above screenshot of the implant code shared by Cisco Talos we found another method that can be used to fingerprint the presence of the implant.

curl -k "https://DEVICEIP/%25"

Using the %25 (percent encoded percent), we meet the conditions specified in the extra location check. This will cause the server to respond with a different HTTP response than it normally would when the implant is not running.

There are currently three known versions of the implant. As of 1 November 2023, the implant is named BadCandy by Cisco Talos 1.

BadCandy Implant v1 / v2 response

A telltale of implant operation is a <head><title>404 Not Found</title></head> in the body. An example HTTP body is as such:

$ curl -k 'https://DEVICEIP/%25'
<html>
<head><title>404 Not Found</title></head>
<body bgcolor="white">
<center><h1>404 Not Found</h1></center>
<hr><center>nginx</center>
</body>
</html>

BadCandy Implant v3 response

The third variant returns the login page rather than the 404. As one would still normally expect a javascript redirect rather than this login page, we can still determine the presence of the implant by checking whether or not a login page is returned:

curl -k 'https://DEVICEIP/%25'
<!DOCTYPE html>
<html>
        <!--
        Copyright (c) 2015-2019 by Cisco Systems, Inc.
        All rights reserved.
        -->
        <head lang="en">
                <meta charset="UTF-8">
                <meta http-equiv="X-UA-Compatible" content="IE=edge">
                <title id="loginTitle"></title>

We found different login responses in our scanning results, and ended up with the name="Username" string as an identifier to determine whether or not a login page is being returned.

Other responses

If the implant is not present, you will get a different response. For example:

$ curl -k 'https://DEVICEIP/%25'
<script>window.onload=function(){ url ='/webui';window.location.href=url;}</script>

Script to check for compromise

We created a small script that checks for compromise using the above fingerprinting method. Script can be found here:

Example usage:

$ pip3 install requests

$ python3 iocisco.py 192.168.1.1
[!] Checking http://192.168.1.1/%25
    WARNING: Possible implant found for 192.168.1.1 (impant v3)! Please perform a forensic investigation!
[!] Checking https://192.168.1.1/%25
    WARNING: Possible implant found for 192.168.1.1 (implant v3)! Please perform a forensic investigation!

It is also possible to scan a list of hosts, seperated by newlines.

$ python3 iocisco.py --file cisco-ips.txt

References

Footnotes

  1. https://blog.talosintelligence.com/active-exploitation-of-cisco-ios-xe-software/ 2

  2. https://www.bleepingcomputer.com/news/security/number-of-hacked-cisco-ios-xe-devices-plummets-from-50k-to-hundreds/

  3. https://twitter.com/onyphe/status/1715633541264900217

cisco-ios-xe-implant-detection's People

Contributors

fox-srt avatar krlplm avatar maxgroot avatar yunzheng avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

cisco-ios-xe-implant-detection's Issues

Are we 100% sure about this new way of fingerprinting?

So, out of curiosity, I downloaded and built openresty, and added the location configuration:

mark@bleep:~/.build/openresty/nginx$ cat conf/nginx.conf
worker_processes  1;
daemon off;
events {
    worker_connections  1024;
}

http {
    server {
        listen       9999;
        server_name  localhost;
         location ~* % {
            add_header Content-Type text/html;
            add_header Cache-Control 'no-cache, no-store, must-revalidate';
            add_header Pragma no-cache;
            add_header Strict-Transport-Security "max-age=3153600; includeSubdomains";
            return 404;
        }
        location / {
            root   html;
            index  index.html index.htm;
        }
    }
}

When I run openresty:

mark@bleep:~/.build/openresty/nginx$ ../bin/openresty -p `pwd` -c conf/nginx.conf`)

And curl the endpoint, I don't get those headers that are supposed to be added, I get the default openresty 404:

mark@bleep:~$ curl 'localhost:9999/%25' -vv
*   Trying 127.0.0.1:9999...
* Connected to localhost (127.0.0.1) port 9999 (#0)
> GET /%25 HTTP/1.1
> Host: localhost:9999
> User-Agent: curl/7.81.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 404 Not Found
< Server: openresty/1.21.4.2
< Date: Wed, 25 Oct 2023 17:06:10 GMT
< Content-Type: text/html
< Content-Length: 159
< Connection: keep-alive
<
<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>openresty/1.21.4.2</center>
</body>
</html>
* Connection #0 to host localhost left intact

I'm guessing the headers aren't added because nginx doesn't want 404's to be cached? I don't really know, but that's beside the point (it sets those headers if you return 200;). If I remove this location statement, and my nginx config looks like this:

mark@bleep:~/.build/openresty/nginx$ cat conf/nginx.conf
worker_processes  1;
daemon off;
events {
    worker_connections  1024;
}

http {
    server {
        listen       9999;
        server_name  localhost;

        location / {
            root   html;
            index  index.html index.htm;
        }
    }
}
mark@bleep:~/.build/openresty/nginx$ ../bin/openresty -p `pwd` -c conf/nginx.conf

And curl that endpoint again:

mark@bleep:~$ curl 'localhost:9999/%25' -vv
*   Trying 127.0.0.1:9999...
* Connected to localhost (127.0.0.1) port 9999 (#0)
> GET /%25 HTTP/1.1
> Host: localhost:9999
> User-Agent: curl/7.81.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 404 Not Found
< Server: openresty/1.21.4.2
< Date: Wed, 25 Oct 2023 17:07:54 GMT
< Content-Type: text/html
< Content-Length: 159
< Connection: keep-alive
<
<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>openresty/1.21.4.2</center>
</body>
</html>
* Connection #0 to host localhost left intact

It's the same error. Could this be a red herring? I don't have direct access to any compromised machines, so I cannot verify anything; I want to make sure before releasing any numbers to the public.

Edit:

One thing that it could be is some other default 404 handler that these devices may have, which may differ,

Bash Script

Wrote a bash script today to run on a linux jumpbox. A co-worker linked your post with new findings about upgrade to implanted devices, so I incorporated them into script. Maybe helpful for folks that don't have python installed in their org. Thanks for sharing your findings!

#!/bin/bash
# Links:
# https://sec.cloudapps.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-iosxe-webui-privesc-j22SaA4z
# https://www.darkreading.com/attacks-breaches/ten-thousand-cisco-ios-xe-systems-compromised-zero-day-bug
# https://github.com/fox-it/cisco-ios-xe-implant-detection
# https://everything.curl.dev/usingcurl/returns

cmd_count=0
ip_count=0
start_time=$(date +%s)

# List of IP/hostnames
input_file="curl_IPs-FEW.txt"

# Output results to file
output_file="results.txt"

# Protocols
protocols=("http" "https")

# Checks if input file exists
if [[ ! -f "$input_file" ]]; then
    echo "Error: $input_file does not exist."
    exit 1
fi

# Clear results files
> "$output_file"

# Iterate over each line in the input_file
while IFS= read -r line; do
    # Remove whitespace from list of IP/hostname ... just in case
    host=$(echo "$line" | tr -d '[:space:]')

    ip_count=$((ip_count +1))
    
    for protocol in "${protocols[@]}"; do
        full_url="$protocol://$host"

        # Assemble
        # cmd="curl -k -X POST $full_url/webui/logoutconfirm.html?logon_hash=1 --max-time 10"
        # cmd="curl -k -H \"Authorization: 0ff4fbf0ecffa77ce8d3852a29263e263838e9bb\" -X POST $full_url/webui/logoutconfirm.html?logon_hash=1 --max-time 10"
        cmd="curl -k $full_url/%25 --max-time 10"

        # Display command being run to console
        echo "Running: $cmd"

        # Execute command and capture both standard and error output to variable for logic handling
        output=$(eval "$cmd" 2>&1)

        # Record results that timeout after 10 seconds
        if [ $? -eq 28 ]; then
            timeout="Timed out after 10 seconds: $full_url"
            echo "$timeout" >> "$output_file"
            echo "-----------------------------------------------------------------------------------" >> "$output_file"
        # Only record results with an active http server
        elif echo "$output" | grep -q "<html>" || echo "$output" | grep -q "<script>"; then
            echo "Running: $cmd" >> "$output_file"
            echo "$output" >> "$output_file"
            echo "-----------------------------------------------------------------------------------" >> "$output_file"
        fi

        cmd_count=$((cmd_count + 1))
    done

done < "$input_file"

end_time=$(date +%s)
runtime=$((end_time - start_time))
echo "Start Time: $(date -d @$start_time)" >> "$output_file"
echo "Total IPs Checked: $ip_count" >> "$output_file"
echo "Total Commands Executed: $cmd_count" >> "$output_file"
echo "Total elapsed time: $runtime seconds" >> "$output_file"
echo "The results have been saved to $output_file ... Total runtime: $runtime seconds."

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.