2.1.1 vscode-lua-format

0 23
I. SummaryThe Luci system is an open-source system written in lua language, main...

I. Summary

The Luci system is an open-source system written in lua language, mainly introducing some auditing techniques for it. Let's talk about the tool usage in the preparation phase first, and then discuss some ideas from code auditing. I hope that the readers who finish reading this article can gain something from it.

II. Preparation Phase

Code auditing is an indispensable process for vulnerability挖掘, unless black-box testing. This code is not limited to the lua language we will talk about below, but also includes php, java, and even python, as well as the pseudo-C code that often gives binary hands a headache in daily CTF. Through code auditing, we can understand the logic of the entire program development and the process of interface calls, etc., and then find the defects of the program. These defects can cause Denial of Service at a light level, and direct Remote Code Execution (RCE) at a serious level.

Tool usage

The saying goes, 'Sharpen the blade before cutting wood; a good auditing tool is crucial for code auditing. Using the tool well can often make us handle things with ease. The tool I recommend here is VS Code, which I believe is not unfamiliar to everyone reading this. Visual Studio Code (abbreviated as VS Code) is a free code editor developed by Microsoft that supports various operating systems such as Windows, Linux, and macOS. Its strength mainly lies in the numerous powerful plugins it offers. Here, I mainly introduce two, namely vscode-lua-format and lua.

2.1.1 vscode-lua-format

We found that some firmware extracted from the file system has lua code in the lua files that are all garbled and ugly, which undoubtedly brings great difficulties to our code audit work. This plugin can beautify the code, making it look clearer and more impressive!

Analysis of Vulnerability Mining in LuCI System

2.1.2 lua

If the code formatting plugin is called the cornerstone, then this plugin can be called the auxiliary. This plugin allows you to view the calling relationships between various functions, making it easier to understand the basic logic of the program.

1654740241_62a15511287e48af724c1.png!small?1654740241644

Extract the file system

Firstly, try to obtain the firmware, which can be downloaded from some official websites or can be purchased to extract flash from the device. After obtaining the firmware, you can directly extract the file system with binwalk, or if you encounter an ubi file system, you can use the ubireader_extract_images tool to extract it.

1654740252_62a1551c3eecc8f0e0000.png!small?1654740253316

3. Code Audit

Find the attack surface

There are generally two main ideas: one is to find vulnerabilities from the positive angle, which is to first find the interfaces of various functions of the website, and then according to the interface names, find the corresponding logic code for audit. The advantage of this method is that it covers more comprehensively, but the drawback is also obvious, as the interfaces of a website may be very large, making it麻烦 to audit, and it is also impossible to find hidden interfaces.

There is also another approach from the reverse angle to find vulnerabilities, which is to first find dangerous functions in the code, and then globally search for which interface called it, thereby finding the corresponding code to audit. The only drawback of this method is that it is not comprehensive enough, and sometimes it may miss some interfaces with logical vulnerabilities. However, the biggest advantage is that it targets the problem directly, finding the vulnerable points. Therefore, we mainly introduce this method here.

Understand the logic of interface calls

To find code vulnerabilities in the corresponding interface, it is first necessary to understand the code logic of the interface call, then to audit the corresponding code. After we clarify the logic, we can find some hidden interfaces. These are interfaces that exist in the background with corresponding code, but developers have deleted the interface code on the front-end for some reasons. Such hidden interfaces exist. This is the powerful aspect of code audit, which cannot be matched by black-box testing.

For example, let's take a router from a certain manufacturer.

//post package


POST /stok=89eb18a6314226c045d82175589ff4a1/ds HTTP/1.1

Host: 192.168.0.1

Content-Length: 132

Origin: http://192.168.0.1

Connection: close

Referer: http://192.168.0.1/


{"method":"add","ipgroup":{"table":"rule_ipgroup","para":{"flag":"user","name":"aa","rule_scope":["---"],"comment":"aa","ref":"0"}}}


//Corresponding code

function index()

register_module("ipgroup", "ipgroup")

local e = require("luci.model.uci")

local e = e.cursor()

register_keyword_set_data("ipgroup", "rule_ipgroup", "rule_ipgroup_set_data")

register_keyword_add_data("ipgroup", "rule_ipgroup", "rule_ipgroup_add_data")

register_keyword_del_data("ipgroup", "rule_ipgroup", "rule_ipgroup_del_data")

function rule_ipgroup_add_data(t, l, l, l)

local l = p.cursor()

local r = e.ipgroup_table_count_get_by_key_value("flag", "user")

local l = l:get_profile("ipgroup", "group_max") or s

if t.flag == "user" and r >= l then return n.ETABLEFULL end

local l = e.ipgroup_table_entry_get_is_exist("name", t.name)

if l ~= false then return n.EENTRYEXIST end

t.secname = t.name

t.flag = t.flag or "user"

t.comment = t.comment or ""

if t.comment ~= nil then t.comment = string.gsub(t.comment, "'", "''") end

if t.rule_scope == nil then t.rule_scope = {} end

if t.rule_ipgroup == nil then t.rule_ipgroup = {} end

e.ipgroup_table_entry_insert(t)

local _ = e.ipgroup_table_entry_get_is_exist("name", t.name, "id")

local r = e.ipscope_table_entry_get_id_table_by_name(t.rule_scope)

local l = {}

for e = 1, #r do l[e] = r[e].id end

if #l > 0 then e.relation_table_entry_multi_insert(_[1].id, l) end

local r = e.ipgroup_table_entry_get_id_table_by_name(t.rule_ipgroup)

local l = {}

for e = 1, #r do l[e] = r[e].id end

if #l > 0 then e.group_relation_table_entry_multi_insert(_[1].id, l) end

ipgroup_after_proc("add")

return n.ENONE, {["ipgroup"] = {["name"] = t.name}}

end

From the above, we can see that the value of the method parameter in the JSON data of the post package can be add, set, or del, which correspond to the three functions in the code. Next, ipgroup corresponds to the first parameter of the register_keyword_set_data function, the value of table corresponds to the second parameter, and the third parameter is the function to be executed for registration, and the first parameter passed to the function corresponds to the following string of JSON data.

Search for dangerous functions

After we have clarified the interface call logic, we can now reverse search for vulnerable points. First, let's find his dangerous functions. Unlike auditing pseudo C code, he does not need to consider stack vulnerabilities.

3.3.1 Search for Command Execution Functions

This is the most common method we use to find command injection vulnerabilities, which is to find the command execution functions. In Lua, command execution functions include os.execute, io.popen, etc. The execute function is equivalent to the system() function in C language. The function has a default parameter command, which is to parse the command and then call the system through the parsed result. Popen starts a program prog in an additional process and returns the file handle for prog. In simple terms, this function can call a command (program) and return a file descriptor related to the program, which is generally the output result of the called function.

In the luci framework, in addition to these library functions, there are other wrapping functions in the sys.lua file such as call(), fork_cal()l, exec() etc.


function call(...) return u.execute(...) / 256 end

function fork_exec(...)

local e = n.fork()

if e == 0 then

n.chdir("/")

n.chdir("/")

local e = n.open("/dev/null", "w+")

if e then

n.dup(e, n.stderr)

n.dup(e, n.stdout)

n.dup(e, n.stdin)

if e:fileno() > 2 then e:close() end

end

return n.exec("/bin/sh", "-c", ...)

else

return

end

end

function fork_call(...)

local t = n.fork()

if t == 0 then

local e = n.open("/dev/null", "w+")

if e then

n.dup(e, n.stderr)

n.dup(e, n.stdout)

n.dup(e, n.stdin)

if e:fileno() > 2 then e:close() end

end

return n.exec("/bin/sh", "-c", ...)

elseif t > 0 then

local t, e, n = n.waitpid(t)

if e == "exited" then

return n

else

return nil

end

end

end

function exec(e)

local e = r.popen(e)

local n = e:read("*a")

e:close()

return n

end

3.3.2 Global Search

By using global search, we can find where these dangerous functions are called, and then specifically audit the code

1654740272_62a15530a9939fa28fe6d.png!small?1654740273388

Search for the file upload interface

In openwrt, there is a luci.http.setfilehandler function used for file upload. Let's look at an example of it


function upload_pic()}}

local t = {}

local l, i, _

local c = 0

local d = 200 * 1024

local r = 0

local a = "/www/web-static/resources/authserver/tmpfile"

local h = PORTAL_TEMPL.ATE_DIR

The setfilehandler function (function(i, t, e)) parameter i is a structure that contains parameters such as filename and file size

if not l then

l = io.open(a, "w")

c = 0

end

if t then

c = c + #t

if c <= d then

l:write(t)

else

if 0 == r then

l:close()

o.fork_call("rm -f " .. a)

l = io.open(a, "w")

r = 1

end

end

end

if e then l:close() end

end)

luci.http.formvalue("filename")

The setfilehandler function calls a callback function. If the formvalue function is not called, the setfilehandler function will not be executed, and at least one formvalue function is outside the setfilehandler function. This may be used to find vulnerabilities such as command injection and arbitrary file upload.

Searching for vulnerabilities in binary programs

When auditing the code, in addition to the vulnerabilities in the code itself, we can sometimes find vulnerabilities in the binary programs called by it, such as:


function upload_db()

local _ = 131072

local t = 0

local i

local t = nil

local o = nil

local l = 0

local n = e.ENONE

luci.http.setfilehandler(function(r, a, s)

if not i then

if r then

if r.name == "isp_database" then

t = p

o =

"./lib/isp_route/isp_route.sh && isp_route_restore_database"

elseif r.name == "user_database" then

t = d

o =

". /lib/isp_route/isp_route.sh && isp_restore_user_database"

end

end

if t == nil then

n = e.EISPDBNULLFILENAME

return

end

i = io.open(t, "w")

l = 0

end

if a then

l = l + #a

if l <= _ then

i:write(a)

else

n = e.EISPDBTOOLARGE

return

end

end

if s then i:close() end

end)

luci.http.formvalue("trigger-parser")


This code calls isp_route.sh, and its parameter isp_route_restore_database is something we can control. Let's take a look at the content of the sh script



isp_route_restore_database()

{

if [ ! -f $ISP_DATABASE_TMP ];then

return 1;

fi


/usr/sbin/isp_route_db system -c


if [ $? != "0" ]; then

logger -t isp -p warn "The isp system database's format is invalid."

rm $ISP_DATABASE_TMP -f

return 1;

fi


#Move the tmp isp database to /etc/nouci_config/dbs

mv $ISP_DATABASE_TMP $ISP_DATABASE_DIR -f


state=`uci get isp_route.global.state 2>/dev/null`

[ "$state" == "on" ] && /etc/init.d/isp_route stop


/usr/sbin/isp_route_db system -b


if [ "$state" == "on" ]; then

/etc/init.d/isp_route start

else

for i in $isp_tables; do

ipset destroy $i 2>/dev/null

done

fi


isp_restore_user_database()

{

if [ ! -f $ISP_USER_DB_TMP ];then

return 1;

fi


#check the valid of user's database

/usr/sbin/isp_route_db user -c


if [ $? != "0" ]; then

logger -t isp -p warn "The isp user database's format is invalid."

rm $ISP_USER_DB_TMP -f

return 1;

fi


#Move the tmp isp database to /etc/nouci_config/dbs

mv $ISP_USER_DB_TMP $ISP_USER_DB_DIR -f


state=`uci get isp_route.global.state 2>/dev/null`

[ "$state" == "on" ] && /etc/init.d/isp_route stop


/usr/sbin/isp_route_db user -b


if [ "$state" == "on" ]; then

/etc/init.d/isp_route start

else

ipset destroy ISP_USER_DEFINE 2>/dev/null

fi

This script calls a binary program called isp_route_db, and at this point, we can continue to use ida to analyze this program

At this point, we can audit the program as usual to see if there are binary vulnerabilities such as command injection and stack overflow.

Looking for front-end vulnerabilities

There are many interfaces on the back-end, so they are relatively easy to exploit. However, in most cases, vulnerabilities on the back-end are not very significant. If we want to expand the harm of the vulnerability, naturally, we need to look for vulnerabilities that do not require login authentication. At this point, we need to audit vulnerabilities in the front-end. Unfortunately, the front-end of general routers usually only has one login interface. We can find and audit this interface:

//post package

{"method":"do","login":{"username":"xxxxx","password":"xxxxx"}}

//Corresponding code

if n["query_auth_log"] then

if n.method ~= "do" then

r[e.NAME] = e.EINVARG

write_json(r)

return false

end

return action_get_unauth_log(n["query_auth_log"])

end

if n["get_domain_array"] then

if n.method ~= "do" then

r[e.NAME] = e.EINVARG

write_json(r)

return false

end

return action_get_domain_array(n["get_domain_array"])

end

local a, c = l.is_locked()

if a then

r[e.NAME] = e.EUNAUTH

r.data = get_unauth_data(c)

write_json(r)

return false

end

if "" ~= i then

luci.http.redirect("/")

return false

end

if n.login then

if n.method ~= "do" then

r[e.NAME] = e.EINVARG

write_json(r)

return false

end

return action_login(n.login)

end

if (n["administration"] and n["administration"]["set_pwd_before_login"]) or

n["set_password"] then

if n.method ~= "do" then

r[e.NAME] = e.EINVARG

write_json(r)

return false

end

if n["set_password"] then

local e = n["set_password"]["username"]

local t = n["set_password"]["password"]

n["set_password"] = nil

n["administration"] = {}

n["administration"]["set_pwd_before_login"] = {}

n["administration"]["set_pwd_before_login"]["username"] = e

n["administration"]["set_pwd_before_login"]["password"] = t

end

return t.ds(n)

end

We found that in the process of searching for the login interface, we also found other front-end interfaces

//post package

{"method":"do","query_auth_log":{"xxx":"xxxxx","xxx":"xxxxx"}}

{"method":"do","get_domain_array":{"xxx":"xxxxx","xxx":"xxxxx"}}



4. Summary

In summary, we take a router from a certain manufacturer as an example to introduce the vulnerability挖掘 techniques of the luci system, and explain from the perspective of code audit how to dig some interface vulnerabilities, and please correct me if there is anything wrong in the explanation.

你可能想看:
最后修改时间:
admin
上一篇 2025年03月25日 16:43
下一篇 2025年03月25日 17:06

评论已关闭