metasploit-framework icon indicating copy to clipboard operation
metasploit-framework copied to clipboard

Metasploit Generated SSL/TLS Certificates Trivial to Fingerprint

Open jbaines-r7 opened this issue 2 years ago • 4 comments

I was working on a module that uses the Msf::Exploit::Remote::HttpServer mixin. While debugging/testing the module, I noticed the Metasploit generated self-signed certificate contained some pretty silly values. For example:

albinolobster@ubuntu:~$ curl -kv https://10.9.49.248:8443/version.prop
*   Trying 10.9.49.248:8443...
* Connected to 10.9.49.248 (10.9.49.248) port 8443 (#0)
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384
* ALPN, server did not agree to a protocol
* Server certificate:
*  subject: C=US; ST=TN; O=Effertz-Kozey; OU=redundant; CN=effertz.kozey.info; [email protected]
*  start date: Jun 28 08:51:26 2020 GMT
*  expire date: Jun 27 08:51:26 2027 GMT
*  issuer: C=US; ST=TN; O=Effertz-Kozey; OU=redundant; CN=effertz.kozey.info; [email protected]
*  SSL certificate verify result: self-signed certificate (18), continuing anyway.
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
> GET /version.prop HTTP/1.1
> Host: 10.9.49.248:8443
> User-Agent: curl/7.81.0
> Accept: */*

Specifically, O=Effertz-Kozey; OU=redundant; CN=effertz.kozey.info; [email protected] seemed pretty funny to me. Out of curiosity, I went hunting to see where those values come from (like I'm sure many before me have). The answer was pretty straight forward: generated certificates come out of lib/msf/core/cert_provider:

    def self.rand_vars(opts = {})
      opts ||= {}
      opts[:cc] ||= 'US'
      opts[:st] ||= Faker::Address.state_abbr
      opts[:loc] ||= Faker::Address.city
      opts[:org] ||= Faker::Company.name
      opts[:ou] ||= Faker::Hacker.send(%w{noun verb adjective}.sample.to_sym).gsub(/\W+/,'.')
      opts[:cn] ||= opts[:org].downcase.gsub(/and/,'').gsub(/\W+/,'.') + '.' +  Faker::Internet.domain_suffix
      opts[:email] ||= "#{opts[:ou]}@#{opts[:cn]}"
      opts
    end

    def self.ssl_generate_subject(opts = {})
      opts = self.rand_vars(opts)
      subject = ""
      subject << "/C=#{opts[:cc]}" if opts[:cc]
      subject << "/ST=#{opts[:st]}" if opts[:st]
      subject << "/O=#{opts[:org]}" if opts[:org]
      subject << "/OU=#{opts[:ou]}" if opts[:ou]
      subject << "/CN=#{opts[:cn]}" if opts[:cn]
      subject << "/emailAddress=#{opts[:email]}" if opts[:email]
      subject
    end

As we can see, rand_vars makes heavy use of the Faker mixin. After looking at it for a bit, I started to wonder how random this data is. Of particular interest was the organizational unit's use of Faker::Hacker because the OU I saw in my tests were... odd. Here are the potential values from Faker's hacker.yml

en:
  faker:
    hacker:
      abbreviation: [TCP,HTTP,SDD,RAM,GB,CSS,SSL,AGP,SQL,FTP,PCI,AI,ADP,RSS,XML,EXE,COM,HDD,THX,SMTP,SMS,USB,PNG,SAS,IB,SCSI,JSON,XSS,JBOD]
      adjective: [auxiliary,primary,back-end,digital,open-source,virtual,cross-platform,redundant,online,haptic,multi-byte,bluetooth,wireless,1080p,neural, optical,solid state,mobile]
      noun: [driver,protocol,bandwidth,panel,microchip,program,port,card,array,interface,system,sensor,firewall,hard drive,pixel,alarm,feed,monitor,application,transmitter,bus,circuit,capacitor,matrix]
      verb: [back up,bypass,hack,override,compress,copy,navigate,index,connect,generate,quantify,calculate,synthesize,input,transmit,program,reboot,parse]
      ingverb: [backing up,bypassing,hacking,overriding,compressing,copying,navigating,indexing,connecting,generating,quantifying,calculating, synthesizing,transmitting,programming,parsing]

Where Metasploit specifically uses the adjective, noun, and verb... which is not all that random. In fact, thinking about this a little bit, I figured I could probably fingerprint some internet-facing Metasploits due to this somewhat-odd and not all that random data. That turns out to be true. Here is a small sampling:

I could go on, but hopefully the point is made. Due to the operators poor operational security (they should do better and provide their own certs) and Metasploit's odd SSL certificate values, we can easily discover dozens of hosts on the internet actively running Metasploit. Actually pretty useful if you are building up a block list, I imagine... and I hope someone does blocklist the aforementioned addresses.

More importantly though, I also figured I could probably write a Suricata rule that would flag Metasploit usage on the wire (of course this assume TLS < 1.3). I wrote the following monstrosity... but it works. Attached pcap as well if anyone wants to play with it:

alert tls any any -> any any ( \
    msg:"Metasploit Generated TLS Certificate"; \
    flow: established, from_server; \
    ssl_state: client_keyx; \
    tls.cert_subject; content:"C=US"; \
    tls.cert_subject; pcre:"/OU=(auxiliary|primary|back-end|digital|open-source|virtual|cross-platform|redundant|online|haptic|multi-byte|bluetooth|wireless|1080p|neural|optical|solid.state|mobile|driver|protocol|bandwidth|panel|microchip|program|port|card|array|interface|system|sensor|firewall|hard.drive|pixel|alarm|feed|monitor|application|transmitter|bus|circuit|capacitor|matrix|back.up|bypass|hack|override|compress|copy|navigate|index|connect|generate|quantify|calculate|synthesize|input|transmit|program|reboot|parse)/";\
    tls.cert_subject; pcre:"/(auxiliary@|primary@|back-end@|digital@|open-source@|virtual@|cross-platform@|redundant@|online@|haptic@|multi-byte@|bluetooth@|wireless@|1080p@|neural@|optical@|solid.state@|mobile@|driver@|protocol@|bandwidth@|panel@|microchip@|program@|port@|card@|array@|interface@|system@|sensor@|firewall@|hard.drive@|pixel@|alarm@|feed@|monitor@|application@|transmitter@|bus@|circuit@|capacitor@|matrix@|back.up@|bypass@|hack@|override@|compress@|copy@|navigate@|index@|connect@|generate@|quantify@|calculate@|synthesize@|input@|transmit@|program@|reboot@|parse@)/"; \
    classtype:misc-attack; \
    sid:221271;)

metasploit_certificate.zip

TL;DR Metasploit's generated certificates can be used to easily fingerprint Metasploit on the wire (via Suricata, Snort, etc.) or on a service like Censys or Shodan. The root cause is the lack of actual randomness and odd values used in certificate metadata. Unlike something like JARM, this metadata can be used to fingerprint Metasploit with basically zero false positives.

Expectations: Metasploit SSL/TLS server shouldn't be easy to fingerprint / discover. Part of the module review process involves suggestions to add randomness to make things harder to write signatures for (if that works or not is another discussion). Having Metasploit not be obviously fingerprint-able out of the box is, I think, a fair minimum expectation. Obviously, users are free to provide their own certificates but I'm going to guess that most aren't.

Suggested Fix: It seems logical to me to do what we do with User-Agents: find real-world popular self-signed certificates and user their values. We will never be 100% unfindable, but we can do better than this.

jbaines-r7 avatar Jun 24 '22 14:06 jbaines-r7

Yeah the suggested approach sounds good to me. I think we can use this query from Censys to build a list of the most common Organizational Unit names to replace the OU which looks like the largest issue from what you've posted.

Would probably also be a good idea to randomize the country.

What do you think?

smcintyre-r7 avatar Jun 24 '22 16:06 smcintyre-r7

I think only replacing OU is inadequate.

Using Faker.Company.name is also very limited. It only has three forms and relies on a very limited set of names from names.yml. I could have written the same analysis based on that.

I'd personally throw out the use of Faker entirely. There are plenty of things to pretend to be. A few off the top of my head:

  • https://www.shodan.io/search?query=ssl%3A%22CentOS+Web+Panel%22
  • https://www.shodan.io/search?query=ssl%3A%22QNAP%22
  • https://www.shodan.io/search?query=ssl%3A%22Synology+Inc.%22
  • https://www.shodan.io/search?query=ssl%3A%22ASA+Temporary+Self+Signed+Certificate%22

Even just creating a weird localhost cert would be better than what we have, in my opinion:

  • https://www.shodan.io/search?query=ssl%3A%22localhost%22

jbaines-r7 avatar Jun 24 '22 18:06 jbaines-r7

Well claiming to be a device might make it more difficult to find via automation like you're doing but would probably make it easier to identify for a user that visits the site and sees that we're not the device we're claiming to be. Ideally if we're posting a cert saying we're one of those devices, our 404 page and index would be something that resembles that device but that's more complicated. It also occurs to me that imitating those devices may not be great if the cert is used for another TLS service other than HTTP, which I believe is possible.

I guess ideally we'd pass along a service hint for the SSL generation to select a device that provides that service, then the service listener would also attempt to imitate it. That's significantly more work though.

smcintyre-r7 avatar Jun 24 '22 18:06 smcintyre-r7

Hi!

This issue has been left open with no activity for a while now.

We get a lot of issues, so we currently close issues after 60 days of inactivity. It’s been at least 30 days since the last update here. If we missed this issue or if you want to keep it open, please reply here. You can also add the label "not stale" to keep this issue open!

As a friendly reminder: the best way to see this issue, or any other, fixed is to open a Pull Request.

github-actions[bot] avatar Jul 25 '22 15:07 github-actions[bot]

Hi again!

It’s been 60 days since anything happened on this issue, so we are going to close it. Please keep in mind that I’m only a robot, so if I’ve closed this issue in error please feel free to reopen this issue or create a new one if you need anything else.

As a friendly reminder: the best way to see this issue, or any other, fixed is to open a Pull Request.

github-actions[bot] avatar Aug 24 '22 15:08 github-actions[bot]