Auditing and Defending Lua-Based Web ApplicationsThis paper intends to highlight the risk of unvalidated input in Lua-based web applications.Some time ago I wrote about how to detect NoSQL and server-side JavaScript (SSJS) injection vulnerabilities using time-based techniques. JavaScript is still rising and becoming more popular as a platform for server-side code. This time I want to cover security aspects of another language/framework that is being increasingly adopted for web development and that has a lot of potential: Lua.Lua is a powerful language useful for experienced programmers but considered easy for inexperienced programmers at the same time. While Lua has been mostly used for game development, there is a growing ecosystem of Lua web applications and frameworks. Mature web servers, like Apache & Nginx, are the prefered choice for many that are creating or thinking about creating their first Lua-based web applications - together they account for over 70% of the world's web servers and are solid choices to start. Alternative and pioneer Lua web programming tools like CGILua have been around for a while. CGILua runs on top of Apache or any CGI-enabled web server.At Syhunt, we've been using Lua for quite some time as part of our web application security tools and a primary scripting language, and recently we started using internally the Lua modules for the Apache and Nginx web servers, known as mod_lua and ngx_lua respectively. I decided to check myself how insecurely coded Lua web applications could be targeted and how easily the servers in question could be compromised. To perform the tests, I created a small collection of insecure web applications with input validations issues tailored to each web server software.The ResultsMy simulations covered File System Attacks, Cross-Site Scripting (XSS), Local File Inclusion (LFI), OS Command Injection, SQL Injection, Lua Code Injection, CRLF Injection and other top vulnerability classes. I found no way to perform Remote File Inclusion attacks like in PHP, but Lua (at the current state) fails to thwart some attacks that are only possible in older PHP versions. When Lua Pages was available, Log Poisoning was successfully used to upgrade a LFI vulnerability to remote command execution. Time-based techniques were used to spot Lua Code Injection vulnerabilities, and SQL Injection vulnerabilities, which then were used to upload a Lua web shell (backdoor) to the web servers hosting the vulnerable web applications.Coding securely is hard, even for experienced programmers, no matter the programming language you use. If you are a web developer working with Lua, it is highly recommended that you learn about the coding mistakes that impact security and give rise to these vulnerabilities. This paper will go through the most common and important ones. If, instead, you are a pen-tester tasked with securing a website that is using Lua, this paper may be equally useful to you.This paper demonstrates that skilled and even unskilled attackers can take deep advantage of the weaknesses listed above, and quickly uncover the flaws using automated methods. At the end you will also find some tips for hardening the web server configuration.Cross-Site Scripting (XSS)One of the most common vulnerabilities in today's web applications, XSS attacks (CWE-79) can be used to hijack user sessions, conduct phishing attacks, execute malicious code in the context of the user's session, spread malware and more. There are different types of XSS, such as reflected (shown in the examples above) and stored on the server (eg in a database). To prevent XSS attacks, escape user-supplied data on output or validate user input.Print functionsmod_lua: r:puts(), r:write()ngx_lua: ngx.say(), ngx.print()CGILua: cgilua.put(), cgilua.print(), <?=somevar?>Vulnerable code examples: -- /vulnerable.lua?name=<script>alert('XSS');</script> -- Apache & mod_lua function handle(r) local name = r:parseargs().name or "" r:puts(name) end -- Nginx & ngx_lua local name = ngx.req.get_uri_args().name or "" ngx.header.content_type = "text/html" ngx.say(name) -- CGILua local name = cgilua.QUERY.name or "" cgilua.htmlheader() cgilua.put(name) Solution local name = ngx.req.get_uri_args().name or "" name = htmlescape(name) -- see an example at the end of this paper ngx.header.content_type = "text/html" ngx.say(name) SQL InjectionSQL Injection attacks (CWE-89) are used by bad guys to steal information, add or change information in a database, shut down access to a web application, bypass authentication, execute arbitrary commands, among other things. SQL Injection attacks can be effectively mitigated by using strong input validation.In Lua, the LuaSQL library allows web applications to connect to Oracle, MySQL, SQLite, PostgreSQL and other databases and execute SQL statements. mod_lua comes with its own built-in database API for running commands on MySQL, PostgreSQL, SQLite, Oracle and other databases. Whichever option you choose for running SQL commands, be careful with user supplied input and use input validation.The mod_lua documentation recommends the use of prepared statements whenever possible as a way to minimize the risk of SQL injection attacks. See the notes about precautions when working with databases at:http://www.modlua.org/api/database#database_caveatSQL execution functionsLuaSQL's conn:execute()mod_lua's DB API: db:query(), ... (see http://www.modlua.org/api/database)Vulnerable code example: -- mod_lua DB API function handle(r) r.content_type = "text/html" local username = r:parseargs().username or "" local database, err = r:dbacquire("mysql", "host=localhost,user=user,pass=,dbname=dbname") if not err then local sl = 'SELECT * FROM users WHERE username="'..username..'"' local results, err = database:query(r,sl) -- (...) database:close() else r:puts("Could not connect to the database: " .. err) end end -- CGILua & LuaSQL -- /vulnerable.lua?name=John');SQLBELOW -- /shell.lp?cmd=dir cgilua.htmlheader() local sqlite3 = require "luasql.sqlite3" local name = cgilua.QUERY.name local env = sqlite3.sqlite3() local conn = env:connect('mydb.sqlite') local sql = [[ CREATE TABLE sample ('id' INTEGER, 'name' TEXT); INSERT INTO sample values('1','%s') ]] sql = string.format(sql, name) for l in string.gmatch(sql, "[^;]+") do conn:execute(l) end -- (...) conn:close() env:close() -- SQLLite shell upload example for CGILua -- ATTACH DATABASE 'shell.lp' AS shell; -- CREATE TABLE shell.demo (data TEXT); -- INSERT INTO shell.demo (data) VALUES ('<? os.execute(cgilua.QUERY.cmd) ?> The example above works with mod_lua and ngx_lua with some minor changes if a LP preprocessor extension is enabled, but this is not exclusive of SQLLite. All SQL engines can be target of similar attacks. Most commonly targeted is MySQL:http://www.greensql.com/article/protect-yourself-sqli-attacks-create-backdoor-web-server-using-mysqlFilesystem AttacksIn some environments, Lua filesystem functions accept parameters containing null bytes (), but do not handle them correctly, treating the null byte character as string terminator and ignoring all following characters. The reason is that Lua itself, like PHP, is programmed in C and relies on the operating system's filesystem functions (see PHP Null Byte Poisoning http://www.madirish.net/?article=436 andhttp://www.php.net/manual/en/security.filesystem.nullbytes.php).Keep this in mind when dealing with user input, especially when passing it to filesystem functions.File system functionsLua's io library: io.open(), ...LuaFileSystem (lfs) library functions - http://keplerproject.github.io/luafilesystem/manual.htmlmod_lua: r:mkdir(), r:mkrdir(), r:rmdir(), r:sendfile(), r:touch(), etcVulnerable example: -- Nginx & ngx_lua -- /vulnerable.lua?file=/etc/passwd -- /vulnerable.lua?file=c:\boot.ini ngx.header.content_type = "text/html" local file = ngx.req.get_uri_args().file or "" local f = io.open(file..".txt") local result = f:read("*a") f:close() ngx.say(result) An effective solution to thwart these kinds of attacks is to avoid passing user submitted input to any filesystem API.Local File Inclusion (LFI)Regularly underestimated both by penetration testers and developers, Local File Inclusion vulnerabilities are commonly exploited by attackers to cause source code disclosure or to gain command execution through log poisoning and other methods.Include and similar functionsLua's require(), dofile()CGILua: cgilua.handlelp(), cgilua.lp.include(), cgilua.doif(), cgilua.doscript()Vulnerable examples: -- CGILua -- /vulnerable.lua?prod=/var/log/apache/access_log -- after sending request with user-agent "<? os.execute(cgilua.QUERY.cmd) ?>" local prod = cgilua.QUERY.prod or "" cgilua.htmlheader() cgilua.lp.include(prod..".lp") -- CGILua (2) -- /vulnerable.lua?prod=/var/log/apache/access_log local prod = cgilua.QUERY.prod or "" cgilua.handlelp(prod..".lp") -- CGILua (3) -- /vulnerable.lua?prod=somefile.lua local prod = cgilua.QUERY.prod or "" cgilua.htmlheader() cgilua.doif(prod..".lua") Lua Code InjectionThis type of vulnerability (known as CWE-94) occurs when a developer uses the Lua loadstring() function and passes it untrusted data that an attacker can modify. The loadstring() function will compile the code and return a function that when called has the same effect as executing the string. Attackers can use this to inject arbitraty Lua code that is then executed by the web application.Time-based detection methodsUsing Lua code: /vulnerable.lua?name=John")%20c%3Dos.clock t%3Dc() while c()- t<%3D3 do end%20x%3D("`Using LuaSocket: /vulnerable.lua?name=John")%20require"socket".select(nil,nil,3)%20x%3D("Using the sleep function