CS253 Lecture Summaries: Part X: Code Injection
XSS is an example of code injection that’s on the client, here we look at server-side code injection.
The basic idea here is we’re combining user data that we can’t trust with code the programmer wrote. When the code processed by the interpreter is a mix of instructions written by the programmer and the data supplied by the user the interpreter might get confused by malicious code that breaks out of data context - typically by supplying syntax which has special significance. Then the attacker input gets intepreted as program instructions, which are executed as though written by the programmer.
Command Injection
In command injection our goal is to execute arbitrary commands on the host OS via a vulnerable application.
Command injection attacks are possible when an application passes unsafe user supplied data (forms, cookies, HTTP headers etc) to a system shell.
Here’s an example in Node
const filename = process.argv[2]
const stdout = childProcess.execSync(`cat ${filename}`)
console.log(stdout.toString())
Now our malicious input could be file.txt; rm -rf /
which is going to execute something nasty.
Here’s an express example:
app.get('/view', (req, res) => {
const { filename } = req.query
const stdout = childProcess.execSync(`cat ${filename}`)
res.send(stdout.toString())
})
So we need to escape that user input correctly.
We can do this in node with the spawnSync
, which will ensure that user input is santized. It takes the process and an array of arguments to the process and makes sure the arguments are escaped correctly.
Similar methods exist in all scripting languages.
SQL Injection
Our goal here is to execute arbitrary queries to the database via a vulnerable application.
We might read sensitive data from the database, modify database data, execute admin operations, issue commands to the OS.
Possible when an app combines unsafe user data with a SQL template.
Here’s an example of vulnerable server code:
const { username, password } = req.body
const query = `SELECT * FROM users WHERE username = "${username}"`
const results = db.all(query)
if (results.length > 0) {
const user = results[0]
if (user.password === password) {
//login
}
}
You can test if a site is vulnerable by just adding a "
character to your input, which will cause a syntax error.
If we add --
to our malicious input it means everything afterwards will be treated by the SQL interpreter as a comment, so we escape the syntax error.
We could do something like this: " OR 1=1 --
Now that will always return true, so we’ll select every user in the database.
This is the most common test string for probing SQL injection.
Blind SQL injection is where the application does not output data to the web page, eg it just shows a generic error message instead of data useful to the attacker. It could still be vulnerable to injection.
Our goal as an attacker here is to aks the dtabase true or false questions and determine the answer based on the application’s response.
Harder to exploit, but not impossible.
One way is content-based - if page responds differently based on if the query matches something or not, you can use this to ask yes or no questions.
Another is time-based - make the db pause for a specified amount of time when the query matches, otherwise return immediately. Timings are observable.
The problem here is that we’ve allowed the server to make arbitrary queries on the db.
For ease of development we let the application see anything in the database.
Solutions:
Never build SQL queries with string concatenation - we can use ORMs or Parameterized SQL. Call a safe function to insert the user string into the query.
Safer to use this rather than trying to escape yourself.