기본 콘텐츠로 건너뛰기

[2020-angstromCTF] web - A peculiar query write-up

[2020-angstromCTF] web - A peculiar query write-up

English write-up

UI seems like below.

If you click the 'the source' link, you can get back-end source code.

const express = require("express"); const rateLimit = require("express-rate-limit"); const app = express(); const { Pool, Client } = require("pg"); const port = process.env.PORT || 9090; const path = require("path"); const client = new Client({ user: process.env.DBUSER, host: process.env.DBHOST, database: process.env.DBNAME, password: process.env.DBPASS, port: process.env.DBPORT }); async function query(q) { const ret = await client.query(`SELECT name FROM Criminals WHERE name ILIKE '${q}%';`); return ret; } app.set("view engine", "ejs"); app.use(express.static("public")); app.get("/src", (req, res) => { res.sendFile(path.join(__dirname, "index.js")); }); app.get("/", async (req, res) => { if (req.query.q) { try { let q = req.query.q; // no more table dropping for you let censored = false; for (let i = 0; i < q.length; i ++) { if (censored || "'-\".".split``.some(v => v == q[i])) { censored = true; q = q.slice(0, i) + "*" + q.slice(i + 1, q.length); } } q = q.substring(0, 80); const result = await query(q); res.render("home", {results: result.rows, err: ""}); } catch (err) { console.log(err); res.status(500); res.render("home", {results: [], err: "aight wtf stop breaking things"}); } } else { res.render("home", {results: [], err: ""}); } }); app.listen(port, function() { client.connect(); console.log("App listening on port " + port); });

Code is written in javascript, so the server platform is nodejs.

There is simple sql execution function, it filters some special chars.

It filters single-quotor, double-quotor, dash and point.

If one of bad-char appears, the all remainders will be substituted to '*' char.

I thought hard how can I bypass the filter, a solution is javascript type confusion.

The server source code assumes that 'req.query.q' data type is string, if you send url querystring like "q[]=value&q;[]=another", on the server side the value is ["value", "another"] which is javascript array type.

Then q[i] is no longer single character but string, so we can bypass the filtering.

The expression ("'or 1=1-- " == "'") is evaluated as false.

Moreover, javascript array object also have method "slice" like string. The function slightly differ.

If you try to + operation between javascript array and string, the result is string. The array is treated like string.

Then after the expression `slice(0,i) + "*" + slice(i+1, q.length)`, q is now string, we can do the q.substring method below without exception.

I coded query string exploit payload builder in javascript.

function go(payload) { var ret = '?q[]=' + encodeURIComponent(payload); for (var i =1; i < payload.length; i++) { ret += `&q;[]`; } ret += `&q;[]='` return ret; }

For the test, I wrote a query to get the current database name in pg-sql.

It works well!

I tried to get the column name with information_schema table, but q.substring(0, 80) limit our query length to 80, I did another method.

With some functions in pg-sql, we can get the data in 'Criminals' table in json serialized format.

Gotcha!, We've got the flag

actf{qu3r7_s7r1ng5_4r3_0u7_70_g37_y0u}

한글 풀이

UI는 아래와 같이 생겼다.

우측에 the source라는 것을 클릭하면 백엔드 소스도 제공을 해준다.

const express = require("express"); const rateLimit = require("express-rate-limit"); const app = express(); const { Pool, Client } = require("pg"); const port = process.env.PORT || 9090; const path = require("path"); const client = new Client({ user: process.env.DBUSER, host: process.env.DBHOST, database: process.env.DBNAME, password: process.env.DBPASS, port: process.env.DBPORT }); async function query(q) { const ret = await client.query(`SELECT name FROM Criminals WHERE name ILIKE '${q}%';`); return ret; } app.set("view engine", "ejs"); app.use(express.static("public")); app.get("/src", (req, res) => { res.sendFile(path.join(__dirname, "index.js")); }); app.get("/", async (req, res) => { if (req.query.q) { try { let q = req.query.q; // no more table dropping for you let censored = false; for (let i = 0; i < q.length; i ++) { if (censored || "'-\".".split``.some(v => v == q[i])) { censored = true; q = q.slice(0, i) + "*" + q.slice(i + 1, q.length); } } q = q.substring(0, 80); const result = await query(q); res.render("home", {results: result.rows, err: ""}); } catch (err) { console.log(err); res.status(500); res.render("home", {results: [], err: "aight wtf stop breaking things"}); } } else { res.render("home", {results: [], err: ""}); } }); app.listen(port, function() { client.connect(); console.log("App listening on port " + port); });

코드를 보니 nodejs로 백엔드를 작성했다.

간단하게 sql 쿼리를 실행시킬 수 있도록 되어있고, 일부 특수문자들을 필터링하는 것을 알 수 있다.

일단 싱글쿼터를 필터링을 해서, 해당 bad character가 나타나면 나머지 모든 글자들을 *로 바꿔버리는 방식이다.

이 sql 쿼리 필터링을 어떻게 우회할까 고민을 많이 했는데, 정답은 javascript type confusion이었다.

서버코드는 req.query.q가 string일 것을 가정하고 코드가 짜져있는데, url 쿼리스트링에 q[]=value&q;[]=another 와 같은 방식으로 전송하면 서버에는 ["value", "another"]과 같은 javascript array형태로 전송이 되게 된다.

그러면 some(v => v == q[i])라는 필터링에서도 ["'or 1=1-- ", "garbage"] 이런 값이 전송이 되는 경우 filtering이 제대로 되지 않게 된다. "'or 1=1-- " == "'" 는 당연 false가 나오기 때문.

게다가 Javascript array는 slice라는 함수를 동일하게 가지고 있다.

그리고 문자열과 배열간 + 연산을 하게 되면, 배열을 문자열처럼 바뀌어서 concat연산이 되고 그 결과는 문자열이 되어서 아래의 q.substring 함수도 정상적으로 실행을 하게 된다.

이러한 조건에 맞는 query string payload를 만들어주는 js 코드를 작성해서 요청을 보내보았다.

function go(payload) { var ret = '?q[]=' + encodeURIComponent(payload); for (var i =1; i < payload.length; i++) { ret += `&q;[]`; } ret += `&q;[]='` return ret; }

일단 테스트 겸 database name을 알아내는 쿼리를 작성해보았다.

결과가 잘 나온다.

information_schema로 column 명을 알아내려고 했는데, q.substring(0, 80)의 쿼리 길이 제한때문에 잘 안되서 다른 방법을 사용해보기로 했다.

이제 Criminals 테이블의 값을 json형태로 serialize해서 다 빼오는 쿼리를 작성해서 날려보자.

flag를 얻었다.

actf{qu3r7_s7r1ng5_4r3_0u7_70_g37_y0u}

from http://eine.tistory.com/211 by ccl(A) rewrite - 2020-03-19 10:54:05

댓글

이 블로그의 인기 게시물

nodejs 백그라운드 실행 PM2 활용하기

nodejs 백그라운드 실행 PM2 활용하기개발/기타정보개발/기타정보 nodejs 백그라운드 실행 PM2 활용하기//pm2는 프로세스매니저2로 프로세스를 관리해준다 npm install pm2 -g // pm2 설치 pm2 start server.js //실행하고싶은 코드를 작성한다음 실행 pm2 list // pm2로 실행한것들 목록보기 pm2 restart server.js //재시작 pm2 stop server.js//중지 //stop 하면 서버가 중지되지만 pm2 list에는 남아있게된다 //pm2 list 에서도 제거하고싶으면 pm2 delete [id] // id는 pm2 list하면 나옴 , 아니면 pm2 delete server.js //해도됨 pm2 monit //서버들 모니터링도할수있다 pm2 log //서버 로그출력되는것도 볼수있따 pm2 start server.js-o ./log.txt // 이런식으로 실행하면 서버에서 console.log 로 출력되는것을 log.txt파일에 저장할수있다자세한 내용은 공식홈페이지로 !from http://dulki.tistory.com/118 by ccl(S)

NodeJs - MariaDB 연동하기

NodeJs - MariaDB 연동하기NodeJs에서 마리아 DB를 사용해보자.1. 마리아DB 설치마리아DB 홈페이지에서 마리아DB를 설치합니다.2. DB 및 테이블 생성테스트 DB와 테이블을 생성한 후 샘플데이터를 Insert합니다.1 2 3 4 5 6 7 8 9 10 11 12 DROP DATABASE IF EXISTS nodejs_test; CREATE DATABASE nodejs_test; DROP USER IF EXISTS nodejs_admin; CREATE USER nodejs_admin@ '%' IDENTIFIED BY 'admin' ; GRANT ALL PRIVILEGES ON nodejs_test. * to nodejs_admin@ '%' ; USE nodejs_test; DROP TABLE IF EXISTS users; CREATE TABLE users ( user_key INT NOT NULL AUTO_INCREMENT PRIMARY KEY , user_id VARCHAR ( 128 ) UNIQUE NOT NULL ); INSERT INTO users(user_id) VALUES ( 'TEST01' ); cs3. Nodejs 프로젝트 생성CMD 혹은 powershell을 실행 후 workspace로 이동합니다.npm init을 실행합니다.프로젝트 관련 여러 사항을 묻습니다. 적당히 입력하고 넘어갑시다.해당 작업을 수행하면 package.json 파일이 입력된 값에 의해 자동으로 생성됩니다.1 2 3 4 5 6 7 8 9 10 11 12 { "name" : "mariadb-test" , "version" : "1.0.0" , "description" : "mariadb connection testing project." , "main" : "index…