{"action":"closed","number":8625,"pull_request":{"url":"https://api.github.com/repos/rapid7/metasploit-framework/pulls/8625","id":127631843,"html_url":"https://github.com/rapid7/metasploit-framework/pull/8625","diff_url":"https://github.com/rapid7/metasploit-framework/pull/8625.diff","patch_url":"https://github.com/rapid7/metasploit-framework/pull/8625.patch","issue_url":"https://api.github.com/repos/rapid7/metasploit-framework/issues/8625","number":8625,"state":"closed","locked":false,"title":"Remove OpenSSL from Windows Meterp, packet header changes, and TLV packet encryption","user":{"login":"OJ","id":28896,"avatar_url":"https://avatars2.githubusercontent.com/u/28896?v=4","gravatar_id":"","url":"https://api.github.com/users/OJ","html_url":"https://github.com/OJ","followers_url":"https://api.github.com/users/OJ/followers","following_url":"https://api.github.com/users/OJ/following{/other_user}","gists_url":"https://api.github.com/users/OJ/gists{/gist_id}","starred_url":"https://api.github.com/users/OJ/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/OJ/subscriptions","organizations_url":"https://api.github.com/users/OJ/orgs","repos_url":"https://api.github.com/users/OJ/repos","events_url":"https://api.github.com/users/OJ/events{/privacy}","received_events_url":"https://api.github.com/users/OJ/received_events","type":"User","site_admin":false},"body":"Associated Metasploit Payloads PR: https://github.com/rapid7/metasploit-payloads/pull/211\r\nAssociated Mettle PR: < coming shortly I promise, I'm not yet finished >\r\n\r\nThose payloads will need to be built and tested alongside this if anything is going to work.\r\n\r\nThis PR supercedes the previous MSF side PR: https://github.com/rapid7/metasploit-framework/pull/8570, which has now been closed.\r\n\r\n## Description\r\n\r\nThis all began after some of the powers that be (and me too!) decided to remove the OpenSSL library from Windows Meterpreter. As a result of that discussion, @busterb went ahead and did the initial OpenSSL removal work in https://github.com/rapid7/metasploit-payloads/pull/205. As shown in the PR comments, various community members expressed a perfectly valid concern about now having encryption on `reverse_tcp` transports. While we initially felt that the lack of encryption in the short term was something we were happy to live, we instead took it on ourselves to go ahead and satiate this need before changing anything.\r\n\r\nThis is another large set of changes, and comes with a large PR description. You'll have to fortive me. I'll do my best to break up the boring bits with amusing memes, gifs and useful tidbits of information.\r\n\r\nHigh-level rationale goes like this:\r\n\r\n* OpenSSL was huge, and was only used in two scenarios (the Python extension, and for `reverse_tcp` transports).\r\n* The only other transport with transport-level encryption (`reverse_https`) makes use of CSP/Schannel, and so doesn't rely on the OpenSSL lib that comes with `metsrv`.\r\n* Removal of OpenSSL would reduce the size of the `metsrv` DLL by a whopping **80%**! A saving that shouldn't be sniffed at.\r\n* After removing the library, the intention would be to re-add some form of encryption using CSP/Schannel as well.\r\n\r\nIt turned out that we had a few other needs that were in some way related, and given that we don't like to constantly break binary compatibility with old payloads, it was a good idea to be \"in for a penny, in for a pound\" and break everything at once so we didn't have to do it again any time soon. The primary need was to modify the packet header so that it included the Session GUID that has been added recently. This would mean that we would have the ability to associate a packet with a session handler, regardless of the transport that the packet came from, and for us to handle the routing of this packet to the relevant session handler that contains the per-session encryption key. This opens up a world of fun things that we can do down the track (more to come on that in future PRs, but trust me, it's going to be funzies!).\r\n\r\nGiven that we were going to make a point of implementing encryption for `reverse_tcp`, it made more sense to enable encryption at the TLV packet level, rather than binding it to the transport. As a result this work contains not only the removal of OpenSSL, but addition of a bunch of code that enables per-session encryption using encryption tha tis specific to the session, with key sharing via RSA. More on this in a moment. XORing the packet with a per-packet XOR key is still needed to help obfuscate the packet headers, and to make it so that the few packets that might fly in plain view are still obscured.\r\n\r\nFor various reasons (including not updating all the Meterpreters, and needing to have \"raw\" packets to begin with), the packet needed to be modified to indicate whether or not the packet contained encrypted data. Combine that with the session GUID requirement, and we had the need to modify the packet header... resulting in the breaking of binary backwards compatibility.\r\n\r\nGiven that we were breaking back compat, I felt now was the time to also fix up the obscure XOR byte swizzling thing we were doing as a result of attempting to use UINT values on Windows. At this point, all the XOR stuff is done assuming a straight 4-byte array, and the Meterpreter code has been updated to reflect this as well.\r\n\r\n### Packet header changes\r\n\r\nPrior to this PR, the packet header looked like this:\r\n```\r\nValues: [XOR KEY][packet length][packet type][ .... TLV packets go here .... ]\r\nSize:   [   4   ][       4     ][     4     ][ ....          N          .... ]\r\n```\r\nAfter this PR, the packet header looks like this:\r\n```\r\nValues: [XOR KEY][session guid][encryption flags][packet length][packet type][ .... TLV packets go here .... ]\r\nSize:   [   4   ][    16      ][      4       ][       4     ][     4     ][ ....          N          .... ]\r\n```\r\n\r\n* **Session GUID**: This contains the GUID that identifies the session. In the case of stageless payloads, this GUID is the null GUID (all `null` bytes), and a new one is generated and passed to the Meterpreter instance when the session is established. This GUID is now easily accessible outside of the contents of the TLV packets, and as said before this means we can route the packet to the correct handler prior to it being decrypted with the session-specific key.\r\n* **Encryption flags**: This is a DWORD flag set that indicates whether this packet has been encrypted or not (and with what). If not, the structure of the packet from the length onwards is the same as it has always been. However, if the flag is set, the the value after the packet type is changed. For example, if the encryption flag is `AES256` (ie. the value `1`, it will look like this:\r\n```\r\nValues: ... [packet length][packet type][AES IV][ .... encrypted TLV .... ]\r\nSize:   ... [       4     ][     4     ][  16  ][ ....        N      .... ]\r\n```\r\nThere is a 16 byte initialisation vector that is used to being the AES256 decryption process. After those 16 bytes is the full encrypted TLV payload.\r\n\r\nIn short, the packet size has increased from `12` bytes to `32`, and hence needs to be handled as follows:\r\n\r\n1. The full `32` byte header should be downloaded first.\r\n1. The first `4` bytes should be extracted, and then used to de-XOR the remaining 28 bytes _as a single block_.\r\n1. The session GUID can (or will, in future) be used to identify the session handler. This isn't happening yet as we don't have the wiring in place, it just assumes the current session handler is going to handle it.\r\n1. The encryption flags can be referenced to determine if the packet is encrypted and how.\r\n1. The `length` value indicates how many more bytes should be read from the data stream associated with the transport.\r\n1. Once fully downloaded, the entire packet should be XOR'd starting from byte `5` (again, to avoid packet alignment issues).\r\n1. If encrypted, the IV should be extracted from the first `16` bytes, and then the decryption process should happen, converting the payload back into a set of TLV values.\r\n1. The TLV \"packet\" can now be passed off for handling by the session handler with all the values decrypted/decoded/etc.\r\n\r\n### Packet encryption\r\n\r\nAs mentioned before, packets are now encrypted with AES256 in windows. But not _immediately_. The session needs to be established, and MSF will negotiate keys with Meterpreter on the fly. This is done for a few reasons:\r\n\r\n* We don't want to have AES keys baked into payloads (and hence readable by someone untrusted).\r\n* We want to have different keys for every session even if it's from executing the same payload repeatedly.\r\n* We want to have the ability to change the keys on the fly at any time if we should wish.\r\n\r\nThis then begs the question: how do we exchange keys in a \"secure\" manner. At the end of this PR I'll post a rationale behind the approach that we're taking, particularly in an effort to address the \"OMG THIS CAN BE MITM'd\" crowd. The following shows the \"handshake\" process:\r\n\r\n1. An exploit or payload fires in the usual way, resulting in a stager being executed.\r\n1. The stager connects to MSF (or the other way, in the case of `bind` payloads).\r\n1. MSF sends a (substantially smaller) `metsrv` DLL down the wire to the stager.\r\n1. The stager downloads the stage, copies it to RWX memory and invokes it.\r\n1. `metsrv` begins execution and takes over communications from the stager.\r\n1. MSF then begins the AES key negotiation phase:\r\n    1. MSF generates a `2048` bit RSA key pair (both public and private key).\r\n    1. The public key is added to a packet in `pem` format, and the packet is sent to Meterpreter with the command `core_negotiate_aes`.\r\n    1. Meterpreter receives the packet. If the AES negotiation feature does not exist, the \"method not found\" error happens as per usual.\r\n    1. If the command is supported and public key handling is supported (which it is for Windows Meterpreter at this point), then:\r\n        1. The public key is extracted, and parsed.\r\n        1. A 256-bit AES key is generated using a cryptographically secure random number generator.\r\n        1. The AES key is stored along with the session data in Meterpreter's memory.\r\n        1. The AES key is then encrypted using the public key that was given as part of the initial message.\r\n        1. Meterpreter responds to MSF with a packet containing the encrypted AES256 key.\r\n    1. If the command is supported and public key handling is _NOT_ supported (might not be the case, but might be for other Meterpreters):\r\n        1. An AES key is generated and stored along side the Meterpreter session data.\r\n        1. The AES key can be returned in clear text.\r\n    1. MSF receives the result of the command:\r\n        1. If the message was not supported, then no AES key is associated with the session.\r\n        1. If the message was fully supported, MSF decrypts the AES key using the private key that was generated with the public key, and the AES key is stored with the session handler.\r\n        1. If the message was partially supported, MSF copies the plain text AES key and stores it with the session handler.\r\n        1. In all cases, the generated RSA key pair is forgotten.\r\n1. If key negotiation was successful, then all future packets are encrypted using the shared AES key, and the encryption flag is set on the packet.\r\n1. If key negotiation was _not_ successful, future packets are sent in plain text just like they are usually (while still being XOR'd).\r\n\r\nThere are a couple of cases that needed to be considered when it came to packet encryption:\r\n\r\n* The response for packet encryption negotiation couldn't be encrypted because MSF isn't yet aware of the key.\r\n* After migration a session to another process, the negotiation phase needs to be re-run, because the AES key is not part of the configuration data (intentional). As a result, encrypt keys are cleared once migration is done and a new key is generated. This is all done prior to any extensions being reloaded, or any other important steps being taken.\r\n* When a transport shuts down (as a result of `sleep` or transport switch), MSF believes the session to be dead. When the session comes back, MSF again negotiates a new AES key, as it doesn't have any tracking of the previous AES key. This might change down the track if the team decides to track AES across the session (personally I don't see the need -- more keys, more pain for IR!).\r\n\r\nThese cases have been considered and coded for.\r\n\r\n## Verification\r\n\r\nI am sorry. I am so, so sorry for whoever has to do this. But, sometimes work sucks! This is one of those times.\r\n\r\nThese verification steps should be done for each of the Meterpreters that exist (yeah.. I know, right?), and for each transport:\r\n\r\n- [x] Create a staged handler and a stageless handler.\r\n- [x] Create a staged payload and a stageless payload.\r\n- [x] Execute the payloads and make sure that the sessions correctly establish, do not crash, and can be interacted with.\r\n- [x] Validate the channels still work by invoking a shell and interacting with bash/cmd.exe/whatever.\r\n- [x] Validate that the loading of extensions still works.\r\n- [x] On Windows, migrate across process boundaries to make sure the migration process still works.\r\n- [x] Make sure that the session comes back after `sleep` or `transport` switching.\r\n- [x] Make sure that stageless connections can connect to staged listeners and that the stage is ignored (the session should still connect correctly).\r\n- [x] Use both `sessions -x` and `sessions -v` and make sure the encryption indicator is accurate.\r\n- [x] Use both the powershell and python extensions, and make sure that the Meterpreter bindings still work.\r\n\r\nFor non-HTTPS payloads on Windows:\r\n\r\n- [x] Using wireshark, capture some traffic of the session and make sure that the content is not just XOR'd but encrypted as well (ie. is junk when you XOR the TLV packet with the first 4 bytes of the packet).\r\n- [x] using wireshark, capture some traffic from reestablished or migrated sessions and make sure the packets are still encrypted.\r\n\r\n## ERMAGHERD!! MAN IN THE MIDDLE!!\r\n\r\nThe most common question that has come up so far, and probably the most common that will come up down the track, is:\r\n\r\n> Isn't this vulnerable to MITM attacks?\r\n\r\nI'm glad you asked. At leaset, I think I am.\r\n\r\n### The Short Answer\r\n\r\nYes! but this is no different to before ripping out OpenSSL.\r\n\r\nIf you don't believe me, read the next answer.\r\n\r\n### The Long Answer\r\n\r\nThere are two types of MITM attacks that should be considered if we're going to be completely thorough: active MITM and passive MITM.\r\n\r\n* Active means that someone is between MSf and Meterpreter, and can interfere with the traffic as it goes across the wire.\r\n* Passive means that someone is pcap'ing communications and could potentially decrypt the packets down the track.\r\n\r\n#### Active MITM\r\n\r\nThe risk of active MITM is no different to how it was before. Meterpreter over TCP did nothing to validate that the `STARTTLS` was actually going to MSF, it just trusted that the 'thing on the other end' was MSF and did the handshake with it before sending it data. It didn't know for sure that MSF was listening, it could instead be someone malicious that is tapping the content of the communications in plain text. If active MITM is a concern now, it should have been a concern before. The risk is no different.\r\n\r\nWhat can we do about it? Actually not a great deal. In order to avoid active MITM threats, we need a way of validation that the \"thing on the other end\" is what we expected, and validating that in the context of Meterp that was just brought to life via an exploit in an unknown network isn't exactly easy. It's fair to assume that the network is hostile, and that any means of validation we have isn't reliable. In the case of HTTPS connections, certificates are validated against a certificate store that references a hierarchy of authorities that can be verified. We just don't have this capability. The closest thing we have is baking a certificate of some kind into Meterpreter and validating the keys that come from MSF against that cert. This is again moot though, because if we have Active MITM going on, then that certificate can be replaced on the wire with a malicious one, and we're back to where we started.\r\n\r\nIt's fair to say that if you are currently being actively MITM'd, you have literally **zero points of trust**. You can choose to trust nothing, and shut yourself down, or you can do what you've always done, and accept the session and carry on with the assessment.\r\n\r\n#### Passive MITM\r\n\r\nThis is a different beast, as it doesn't muck with the traffic. The concern here is that the packets captured can be analysed and decrypted later on. Assuming that we're not also being Actively MITM'd we're in a position to prevent the pcap from being decrypted, as we've got the equivalent of HTTPS with PFS enabled. The only differences are:\r\n\r\n* We generate the ephemeral keys ourselves.\r\n* We don't have the ability validate the keys like we do with HTTPS.\r\n\r\nFor someone to decrypt the traffic, they need to get hold of the AES keys. In order to get hold of the AES keys, they need to be able to get access to private key that was generated by MSF (again, we're assuming no active MITM where keys can be switched). For an attacker to get access to the private RSA key, the attacker's machine must be compromised. However, given that the attacker's machine does not store the RSA key pair for any longer than the duration of the request, compromsing the attacker's machine would be useless as the key is no longer there to be found. In the case of normal HTTPS, PSF is supported by generating ephemeral keys that are signed by the RSA private key associated with the HTTPS certificate. If the HTTPS certificate is later accessed, the private key can not be used to decrypt the traffic, as it's public key not the one that was used to encrypt it in the first place. We're doing the same thing, just without signing the key.\r\n\r\nI honestly feel that the risk of passive MITM resulting in the compromise of data going across the wire is as small as it can be.\r\n\r\n## Thanks\r\n\r\nAs always, props to the crew behind the scenes that helped with this (even if it was just the thought process), including @busterb and @sempervictus. Thanks in advance to those people who get involved with the discussion and who help get this over the line.\r\n\r\nLet the discussion commence!\r\n\r\n## Sorry!\r\n\r\nI know, I lied about the memes and gifs!\r\n\r\n## Edit 28th June\r\n\r\nBoth `sessions -x` and `sessions -v` include an indication of whether or not the session is currently utilising packet level encryption. This should be tested as well!","created_at":"2017-06-27T07:54:26Z","updated_at":"2017-08-21T00:14:18Z","closed_at":"2017-08-21T00:14:18Z","merged_at":"2017-08-21T00:14:18Z","merge_commit_sha":"fa292dce9677fa2454757932eaa0724c80057a1f","assignee":{"login":"busterb","id":4108654,"avatar_url":"https://avatars3.githubusercontent.com/u/4108654?v=4","gravatar_id":"","url":"https://api.github.com/users/busterb","html_url":"https://github.com/busterb","followers_url":"https://api.github.com/users/busterb/followers","following_url":"https://api.github.com/users/busterb/following{/other_user}","gists_url":"https://api.github.com/users/busterb/gists{/gist_id}","starred_url":"https://api.github.com/users/busterb/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/busterb/subscriptions","organizations_url":"https://api.github.com/users/busterb/orgs","repos_url":"https://api.github.com/users/busterb/repos","events_url":"https://api.github.com/users/busterb/events{/privacy}","received_events_url":"https://api.github.com/users/busterb/received_events","type":"User","site_admin":false},"assignees":[{"login":"busterb","id":4108654,"avatar_url":"https://avatars3.githubusercontent.com/u/4108654?v=4","gravatar_id":"","url":"https://api.github.com/users/busterb","html_url":"https://github.com/busterb","followers_url":"https://api.github.com/users/busterb/followers","following_url":"https://api.github.com/users/busterb/following{/other_user}","gists_url":"https://api.github.com/users/busterb/gists{/gist_id}","starred_url":"https://api.github.com/users/busterb/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/busterb/subscriptions","organizations_url":"https://api.github.com/users/busterb/orgs","repos_url":"https://api.github.com/users/busterb/repos","events_url":"https://api.github.com/users/busterb/events{/privacy}","received_events_url":"https://api.github.com/users/busterb/received_events","type":"User","site_admin":false}],"requested_reviewers":[{"login":"timwr","id":684924,"avatar_url":"https://avatars2.githubusercontent.com/u/684924?v=4","gravatar_id":"","url":"https://api.github.com/users/timwr","html_url":"https://github.com/timwr","followers_url":"https://api.github.com/users/timwr/followers","following_url":"https://api.github.com/users/timwr/following{/other_user}","gists_url":"https://api.github.com/users/timwr/gists{/gist_id}","starred_url":"https://api.github.com/users/timwr/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/timwr/subscriptions","organizations_url":"https://api.github.com/users/timwr/orgs","repos_url":"https://api.github.com/users/timwr/repos","events_url":"https://api.github.com/users/timwr/events{/privacy}","received_events_url":"https://api.github.com/users/timwr/received_events","type":"User","site_admin":false},{"login":"zeroSteiner","id":2058303,"avatar_url":"https://avatars1.githubusercontent.com/u/2058303?v=4","gravatar_id":"","url":"https://api.github.com/users/zeroSteiner","html_url":"https://github.com/zeroSteiner","followers_url":"https://api.github.com/users/zeroSteiner/followers","following_url":"https://api.github.com/users/zeroSteiner/following{/other_user}","gists_url":"https://api.github.com/users/zeroSteiner/gists{/gist_id}","starred_url":"https://api.github.com/users/zeroSteiner/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/zeroSteiner/subscriptions","organizations_url":"https://api.github.com/users/zeroSteiner/orgs","repos_url":"https://api.github.com/users/zeroSteiner/repos","events_url":"https://api.github.com/users/zeroSteiner/events{/privacy}","received_events_url":"https://api.github.com/users/zeroSteiner/received_events","type":"User","site_admin":false}],"milestone":null,"commits_url":"https://api.github.com/repos/rapid7/metasploit-framework/pulls/8625/commits","review_comments_url":"https://api.github.com/repos/rapid7/metasploit-framework/pulls/8625/comments","review_comment_url":"https://api.github.com/repos/rapid7/metasploit-framework/pulls/comments{/number}","comments_url":"https://api.github.com/repos/rapid7/metasploit-framework/issues/8625/comments","statuses_url":"https://api.github.com/repos/rapid7/metasploit-framework/statuses/fa292dce9677fa2454757932eaa0724c80057a1f","head":{"label":"OJ:transport-agnostic-packet-encryption","ref":"transport-agnostic-packet-encryption","sha":"fa292dce9677fa2454757932eaa0724c80057a1f","user":{"login":"OJ","id":28896,"avatar_url":"https://avatars2.githubusercontent.com/u/28896?v=4","gravatar_id":"","url":"https://api.github.com/users/OJ","html_url":"https://github.com/OJ","followers_url":"https://api.github.com/users/OJ/followers","following_url":"https://api.github.com/users/OJ/following{/other_user}","gists_url":"https://api.github.com/users/OJ/gists{/gist_id}","starred_url":"https://api.github.com/users/OJ/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/OJ/subscriptions","organizations_url":"https://api.github.com/users/OJ/orgs","repos_url":"https://api.github.com/users/OJ/repos","events_url":"https://api.github.com/users/OJ/events{/privacy}","received_events_url":"https://api.github.com/users/OJ/received_events","type":"User","site_admin":false},"repo":{"id":12772618,"name":"metasploit-framework","full_name":"OJ/metasploit-framework","owner":{"login":"OJ","id":28896,"avatar_url":"https://avatars2.githubusercontent.com/u/28896?v=4","gravatar_id":"","url":"https://api.github.com/users/OJ","html_url":"https://github.com/OJ","followers_url":"https://api.github.com/users/OJ/followers","following_url":"https://api.github.com/users/OJ/following{/other_user}","gists_url":"https://api.github.com/users/OJ/gists{/gist_id}","starred_url":"https://api.github.com/users/OJ/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/OJ/subscriptions","organizations_url":"https://api.github.com/users/OJ/orgs","repos_url":"https://api.github.com/users/OJ/repos","events_url":"https://api.github.com/users/OJ/events{/privacy}","received_events_url":"https://api.github.com/users/OJ/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/OJ/metasploit-framework","description":"Metasploit Framework","fork":true,"url":"https://api.github.com/repos/OJ/metasploit-framework","forks_url":"https://api.github.com/repos/OJ/metasploit-framework/forks","keys_url":"https://api.github.com/repos/OJ/metasploit-framework/keys{/key_id}","collaborators_url":"https://api.github.com/repos/OJ/metasploit-framework/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/OJ/metasploit-framework/teams","hooks_url":"https://api.github.com/repos/OJ/metasploit-framework/hooks","issue_events_url":"https://api.github.com/repos/OJ/metasploit-framework/issues/events{/number}","events_url":"https://api.github.com/repos/OJ/metasploit-framework/events","assignees_url":"https://api.github.com/repos/OJ/metasploit-framework/assignees{/user}","branches_url":"https://api.github.com/repos/OJ/metasploit-framework/branches{/branch}","tags_url":"https://api.github.com/repos/OJ/metasploit-framework/tags","blobs_url":"https://api.github.com/repos/OJ/metasploit-framework/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/OJ/metasploit-framework/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/OJ/metasploit-framework/git/refs{/sha}","trees_url":"https://api.github.com/repos/OJ/metasploit-framework/git/trees{/sha}","statuses_url":"https://api.github.com/repos/OJ/metasploit-framework/statuses/{sha}","languages_url":"https://api.github.com/repos/OJ/metasploit-framework/languages","stargazers_url":"https://api.github.com/repos/OJ/metasploit-framework/stargazers","contributors_url":"https://api.github.com/repos/OJ/metasploit-framework/contributors","subscribers_url":"https://api.github.com/repos/OJ/metasploit-framework/subscribers","subscription_url":"https://api.github.com/repos/OJ/metasploit-framework/subscription","commits_url":"https://api.github.com/repos/OJ/metasploit-framework/commits{/sha}","git_commits_url":"https://api.github.com/repos/OJ/metasploit-framework/git/commits{/sha}","comments_url":"https://api.github.com/repos/OJ/metasploit-framework/comments{/number}","issue_comment_url":"https://api.github.com/repos/OJ/metasploit-framework/issues/comments{/number}","contents_url":"https://api.github.com/repos/OJ/metasploit-framework/contents/{+path}","compare_url":"https://api.github.com/repos/OJ/metasploit-framework/compare/{base}...{head}","merges_url":"https://api.github.com/repos/OJ/metasploit-framework/merges","archive_url":"https://api.github.com/repos/OJ/metasploit-framework/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/OJ/metasploit-framework/downloads","issues_url":"https://api.github.com/repos/OJ/metasploit-framework/issues{/number}","pulls_url":"https://api.github.com/repos/OJ/metasploit-framework/pulls{/number}","milestones_url":"https://api.github.com/repos/OJ/metasploit-framework/milestones{/number}","notifications_url":"https://api.github.com/repos/OJ/metasploit-framework/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/OJ/metasploit-framework/labels{/name}","releases_url":"https://api.github.com/repos/OJ/metasploit-framework/releases{/id}","deployments_url":"https://api.github.com/repos/OJ/metasploit-framework/deployments","created_at":"2013-09-12T01:57:53Z","updated_at":"2017-07-31T06:17:44Z","pushed_at":"2017-08-18T06:57:42Z","git_url":"git://github.com/OJ/metasploit-framework.git","ssh_url":"git@github.com:OJ/metasploit-framework.git","clone_url":"https://github.com/OJ/metasploit-framework.git","svn_url":"https://github.com/OJ/metasploit-framework","homepage":"http://www.metasploit.com/","size":319115,"stargazers_count":3,"watchers_count":3,"language":"Ruby","has_issues":false,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":3,"default_branch":"master"}},"base":{"label":"rapid7:master","ref":"master","sha":"f71ca924c4fcb2889d9a1f149852e3192ed75e1a","user":{"login":"rapid7","id":1013671,"avatar_url":"https://avatars1.githubusercontent.com/u/1013671?v=4","gravatar_id":"","url":"https://api.github.com/users/rapid7","html_url":"https://github.com/rapid7","followers_url":"https://api.github.com/users/rapid7/followers","following_url":"https://api.github.com/users/rapid7/following{/other_user}","gists_url":"https://api.github.com/users/rapid7/gists{/gist_id}","starred_url":"https://api.github.com/users/rapid7/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rapid7/subscriptions","organizations_url":"https://api.github.com/users/rapid7/orgs","repos_url":"https://api.github.com/users/rapid7/repos","events_url":"https://api.github.com/users/rapid7/events{/privacy}","received_events_url":"https://api.github.com/users/rapid7/received_events","type":"Organization","site_admin":false},"repo":{"id":2293158,"name":"metasploit-framework","full_name":"rapid7/metasploit-framework","owner":{"login":"rapid7","id":1013671,"avatar_url":"https://avatars1.githubusercontent.com/u/1013671?v=4","gravatar_id":"","url":"https://api.github.com/users/rapid7","html_url":"https://github.com/rapid7","followers_url":"https://api.github.com/users/rapid7/followers","following_url":"https://api.github.com/users/rapid7/following{/other_user}","gists_url":"https://api.github.com/users/rapid7/gists{/gist_id}","starred_url":"https://api.github.com/users/rapid7/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rapid7/subscriptions","organizations_url":"https://api.github.com/users/rapid7/orgs","repos_url":"https://api.github.com/users/rapid7/repos","events_url":"https://api.github.com/users/rapid7/events{/privacy}","received_events_url":"https://api.github.com/users/rapid7/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/rapid7/metasploit-framework","description":"Metasploit Framework","fork":false,"url":"https://api.github.com/repos/rapid7/metasploit-framework","forks_url":"https://api.github.com/repos/rapid7/metasploit-framework/forks","keys_url":"https://api.github.com/repos/rapid7/metasploit-framework/keys{/key_id}","collaborators_url":"https://api.github.com/repos/rapid7/metasploit-framework/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/rapid7/metasploit-framework/teams","hooks_url":"https://api.github.com/repos/rapid7/metasploit-framework/hooks","issue_events_url":"https://api.github.com/repos/rapid7/metasploit-framework/issues/events{/number}","events_url":"https://api.github.com/repos/rapid7/metasploit-framework/events","assignees_url":"https://api.github.com/repos/rapid7/metasploit-framework/assignees{/user}","branches_url":"https://api.github.com/repos/rapid7/metasploit-framework/branches{/branch}","tags_url":"https://api.github.com/repos/rapid7/metasploit-framework/tags","blobs_url":"https://api.github.com/repos/rapid7/metasploit-framework/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/rapid7/metasploit-framework/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/rapid7/metasploit-framework/git/refs{/sha}","trees_url":"https://api.github.com/repos/rapid7/metasploit-framework/git/trees{/sha}","statuses_url":"https://api.github.com/repos/rapid7/metasploit-framework/statuses/{sha}","languages_url":"https://api.github.com/repos/rapid7/metasploit-framework/languages","stargazers_url":"https://api.github.com/repos/rapid7/metasploit-framework/stargazers","contributors_url":"https://api.github.com/repos/rapid7/metasploit-framework/contributors","subscribers_url":"https://api.github.com/repos/rapid7/metasploit-framework/subscribers","subscription_url":"https://api.github.com/repos/rapid7/metasploit-framework/subscription","commits_url":"https://api.github.com/repos/rapid7/metasploit-framework/commits{/sha}","git_commits_url":"https://api.github.com/repos/rapid7/metasploit-framework/git/commits{/sha}","comments_url":"https://api.github.com/repos/rapid7/metasploit-framework/comments{/number}","issue_comment_url":"https://api.github.com/repos/rapid7/metasploit-framework/issues/comments{/number}","contents_url":"https://api.github.com/repos/rapid7/metasploit-framework/contents/{+path}","compare_url":"https://api.github.com/repos/rapid7/metasploit-framework/compare/{base}...{head}","merges_url":"https://api.github.com/repos/rapid7/metasploit-framework/merges","archive_url":"https://api.github.com/repos/rapid7/metasploit-framework/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/rapid7/metasploit-framework/downloads","issues_url":"https://api.github.com/repos/rapid7/metasploit-framework/issues{/number}","pulls_url":"https://api.github.com/repos/rapid7/metasploit-framework/pulls{/number}","milestones_url":"https://api.github.com/repos/rapid7/metasploit-framework/milestones{/number}","notifications_url":"https://api.github.com/repos/rapid7/metasploit-framework/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/rapid7/metasploit-framework/labels{/name}","releases_url":"https://api.github.com/repos/rapid7/metasploit-framework/releases{/id}","deployments_url":"https://api.github.com/repos/rapid7/metasploit-framework/deployments","created_at":"2011-08-30T06:13:20Z","updated_at":"2017-08-20T23:48:26Z","pushed_at":"2017-08-21T00:14:12Z","git_url":"git://github.com/rapid7/metasploit-framework.git","ssh_url":"git@github.com:rapid7/metasploit-framework.git","clone_url":"https://github.com/rapid7/metasploit-framework.git","svn_url":"https://github.com/rapid7/metasploit-framework","homepage":"https://www.metasploit.com/","size":325773,"stargazers_count":9420,"watchers_count":9420,"language":"Ruby","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":5779,"mirror_url":null,"open_issues_count":451,"forks":5779,"open_issues":451,"watchers":9420,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/rapid7/metasploit-framework/pulls/8625"},"html":{"href":"https://github.com/rapid7/metasploit-framework/pull/8625"},"issue":{"href":"https://api.github.com/repos/rapid7/metasploit-framework/issues/8625"},"comments":{"href":"https://api.github.com/repos/rapid7/metasploit-framework/issues/8625/comments"},"review_comments":{"href":"https://api.github.com/repos/rapid7/metasploit-framework/pulls/8625/comments"},"review_comment":{"href":"https://api.github.com/repos/rapid7/metasploit-framework/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/rapid7/metasploit-framework/pulls/8625/commits"},"statuses":{"href":"https://api.github.com/repos/rapid7/metasploit-framework/statuses/fa292dce9677fa2454757932eaa0724c80057a1f"}},"merged":true,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":{"login":"busterb","id":4108654,"avatar_url":"https://avatars3.githubusercontent.com/u/4108654?v=4","gravatar_id":"","url":"https://api.github.com/users/busterb","html_url":"https://github.com/busterb","followers_url":"https://api.github.com/users/busterb/followers","following_url":"https://api.github.com/users/busterb/following{/other_user}","gists_url":"https://api.github.com/users/busterb/gists{/gist_id}","starred_url":"https://api.github.com/users/busterb/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/busterb/subscriptions","organizations_url":"https://api.github.com/users/busterb/orgs","repos_url":"https://api.github.com/users/busterb/repos","events_url":"https://api.github.com/users/busterb/events{/privacy}","received_events_url":"https://api.github.com/users/busterb/received_events","type":"User","site_admin":false},"comments":37,"review_comments":13,"maintainer_can_modify":false,"commits":16,"additions":311,"deletions":141,"changed_files":13}}
{"actor":{"display_login":"busterb"}}