COUCHDB가 뭐야? 나 오라클이랑 MYSQL이랑 저거 뭐냐 NOSQL중에 레디스? 그거밖에 모른ㄴ데
CouchDB는 Apache 소프트웨어 재단에서 개발한 NoSQL 데이터베이스 중 하나로, JSON 기반의 데이터를 저장하고, HTTP를 통해 데이터를 처리 및 조회하는 기능을 제공합니다. CouchDB는 다른 NoSQL 데이터베이스들과 차별화된 몇 가지 특징을 가지고 있습니다.
CouchDB의 주요 특징
- 문서 지향(Document-Oriented):
- CouchDB는 데이터를 문서(document) 단위로 관리합니다.
- 각 문서는 JSON 형식으로 저장되며, 키-값 쌍의 구조를 가집니다.
- SQL 데이터베이스의 테이블과 행 대신, 독립적인 JSON 문서를 사용하여 데이터를 유연하게 저장.
- HTTP/RESTful API 기반:
- CouchDB는 데이터베이스와 상호작용하기 위해 HTTP 프로토콜을 사용합니다.
- 브라우저나 curl 같은 간단한 도구를 사용하여 데이터베이스에 접근 가능.
- 예: 데이터를 조회하려면 단순히 다음과 같이 요청할 수 있습니다:
GET http://localhost:5984/my_database/document_id
- MVCC (Multi-Version Concurrency Control):
- 동시성 제어를 위해 다중 버전 관리 방식을 사용합니다.
- 문서가 수정될 때마다 이전 버전이 유지되며, 충돌 발생 시 이를 자동으로 감지합니다.
- 분산형 데이터베이스:
- CouchDB는 **복제(replication)**와 동기화(synchronization) 기능이 내장되어 있어, 여러 노드 간 데이터를 손쉽게 동기화할 수 있습니다.
- 분산 환경에서 데이터를 관리하기에 적합.
- MapReduce를 통한 쿼리:
- 데이터 조회 및 처리에 MapReduce 기반의 뷰(view)를 제공합니다.
- SQL의 SELECT 문과 비슷한 역할을 하며, 복잡한 쿼리를 효율적으로 실행할 수 있습니다.
- 높은 가용성과 내결함성:
- CouchDB는 설계상 내결함성(fault-tolerant)을 제공하며, 네트워크 분할이나 서버 장애가 발생해도 데이터의 무결성을 유지.
CouchDB와 다른 데이터베이스 비교
CouchDB vs Oracle/MySQL (RDBMS)
특징 CouchDB Oracle/MySQL
데이터 모델 | 문서 지향(JSON) | 관계형(테이블) |
스키마 | 스키마리스(Schema-less) | 스키마 필요 |
쿼리 방식 | MapReduce 뷰, HTTP API | SQL |
확장성 | 수평 확장 우수 | 주로 수직 확장 |
복제 및 동기화 | 강력한 내장 복제 지원 | 복잡한 설정 필요 |
CouchDB vs MongoDB (NoSQL)
특징 CouchDB MongoDB
데이터 모델 | 문서 지향(JSON) | 문서 지향(JSON) |
API | RESTful HTTP API | 자체 쿼리 언어 |
복제/동기화 | 내장 동기화 기능 강력 | 클러스터링 지원 |
사용 용도 | 분산/오프라인-온라인 동기화 | 대규모 데이터 처리 |
CouchDB를 사용해야 하는 이유
- 오프라인-온라인 동기화:
- CouchDB는 모바일 앱이나 로컬 클라이언트에서 데이터를 저장하고, 네트워크가 복구되면 서버와 데이터를 동기화하는 데 탁월.
- RESTful 접근:
- HTTP API로 모든 데이터베이스 작업을 처리하므로, 별도의 데이터베이스 드라이버 없이도 쉽게 통신 가능.
- 스키마리스 데이터 구조:
- 데이터의 구조가 유연하여, 기존 데이터 구조를 변경하지 않고도 새로운 데이터를 추가할 수 있습니다.
- 복잡한 트랜잭션이 필요 없는 경우:
- 금융 시스템처럼 복잡한 트랜잭션 처리보다는, 단순한 데이터 저장 및 검색 작업에 적합.
단점
- 대규모 데이터 처리 한계:
- MongoDB와 같은 다른 NoSQL에 비해 초대규모 데이터 처리에는 덜 적합.
- SQL 지원 부족:
- 관계형 데이터베이스 사용자에게는 SQL 미지원이 불편할 수 있습니다.
- 도구 생태계 제한적:
- MongoDB나 Oracle과 같은 대중적인 데이터베이스에 비해 커뮤니티와 도구가 적음.
CouchDB 설치 및 기본 사용
1. CouchDB 설치
- Docker를 사용:
docker run -d -p 5984:5984 -e COUCHDB_USER=admin -e COUCHDB_PASSWORD=password couchdb
- 또는 패키지 매니저 사용(Ubuntu):
sudo apt install couchdb
2. CouchDB 실행
- CouchDB는 기본적으로 5984 포트에서 실행됩니다.
- 브라우저에서 관리 페이지에 접근:
http://localhost:5984/_utils/
3. 데이터 추가
- 데이터베이스 생성:
- PUT http://localhost:5984/my_database
- 문서 추가:
- POST http://localhost:5984/my_database Content-Type: application/json { "name": "John Doe", "age": 30 }
CouchDB는 어디에 적합한가?
- 모바일/웹 애플리케이션:
- 오프라인-온라인 동기화를 구현할 때 강력.
- 분산 시스템:
- 여러 서버 간 데이터 복제가 필요할 때.
- RESTful 데이터베이스 요구사항:
- HTTP API로 쉽게 통신해야 하는 프로젝트.
결론
- CouchDB는 JSON 문서를 중심으로 데이터를 저장하고, HTTP API를 통해 데이터를 처리하는 NoSQL 데이터베이스입니다.
- Oracle/MySQL과 같은 RDBMS와는 다른 방식으로 데이터를 관리하며, MongoDB와는 동기화 기능에서 차별화됩니다.
- 특정 분산형 데이터베이스 또는 오프라인 동기화 요구사항이 있는 경우 CouchDB를 고려할 만한 가치가 있습니다! 😊
ㅎㅇㅎㅇ 문서형 DB다 신기하네 DB종류 겁나 많네 ㄹㅇㅋㅋ
쿼리방식이 SQL이 아니라 MapReduce 뷰, HTTP API종류로 있다. HTTP로 인젝션해서 문제풀것같다
소스코드 보러가자
docker-compose.yml 파일
services:
nodejs:
build:
context: ./app/
image: nodejs-app
restart: always
ports:
- 3000:3000
environment:
- COUCHDB_USER=admin
- COUCHDB_PASSWORD=password
- FLAG=DH{**FLAG**}
depends_on:
- couchdb
couchdb:
image: couchdb
restart: always
environment:
- COUCHDB_USER=admin
- COUCHDB_PASSWORD=password
이 docker-compose.yml 파일은 Docker Compose를 사용하여 Node.js 애플리케이션과 CouchDB 데이터베이스를 컨테이너로 실행하기 위한 설정 파일입니다. 이 파일의 목적과 주요 구성 요소를 분석하겠습니다.
Docker Compose란?
Docker Compose는 여러 Docker 컨테이너를 정의하고 함께 관리할 수 있는 도구입니다. docker-compose.yml 파일을 사용하여 애플리케이션 서비스, 네트워크, 볼륨 등을 정의합니다. 이 파일을 실행하면 정의된 컨테이너들이 동시에 생성되고 상호작용할 수 있습니다.
구성 요소 분석
1. 서비스 정의
services 아래에 두 개의 서비스가 정의되어 있습니다:
- nodejs: Node.js 애플리케이션을 실행하는 컨테이너.
- couchdb: CouchDB 데이터베이스를 실행하는 컨테이너.
2. Node.js 서비스
nodejs:
build:
context: ./app/
image: nodejs-app
restart: always
ports:
- 3000:3000
environment:
- COUCHDB_USER=admin
- COUCHDB_PASSWORD=password
- FLAG=DH{**FLAG**}
depends_on:
- couchdb
- build:
- Node.js 애플리케이션을 실행하기 위해 ./app/ 디렉토리에서 Docker 이미지를 빌드합니다.
- 이 디렉토리에는 Dockerfile이 있어야 합니다.
- image:
- 빌드한 이미지를 nodejs-app이라는 이름으로 사용합니다.
- restart: always:
- 컨테이너가 비정상 종료되더라도 자동으로 재시작합니다.
- ports:
- 3000:3000: 호스트의 포트 3000을 컨테이너의 포트 3000에 바인딩합니다. Node.js 애플리케이션은 일반적으로 이 포트를 사용합니다.
- environment:
- 환경 변수를 설정합니다. Node.js 애플리케이션에서 CouchDB에 연결하기 위해 사용자 이름과 비밀번호(COUCHDB_USER, COUCHDB_PASSWORD)가 필요합니다.
- FLAG 변수는 보안 관련 데이터를 전달하거나 디버깅 목적으로 사용될 수 있습니다. 예를 들어, CTF(해킹 대회) 환경에서 플래그 값을 제공할 수도 있습니다.
- depends_on:
- nodejs 서비스가 couchdb 컨테이너가 시작된 후에 실행되도록 합니다.
- 이는 CouchDB가 실행 중인지 확인하지 않으므로, 추가적인 healthcheck 설정이 필요할 수 있습니다.
3. CouchDB 서비스
couchdb:
image: couchdb
restart: always
environment:
- COUCHDB_USER=admin
- COUCHDB_PASSWORD=password
- image:
- 공식 couchdb 이미지를 Docker Hub에서 가져옵니다.
- restart: always:
- CouchDB 컨테이너도 비정상 종료 시 자동으로 재시작합니다.
- environment:
- CouchDB의 관리자 사용자 이름(COUCHDB_USER)과 비밀번호(COUCHDB_PASSWORD)를 설정합니다.
동작 흐름
- CouchDB 컨테이너 실행:
- couchdb 컨테이너가 먼저 실행됩니다.
- admin 사용자와 password 비밀번호로 CouchDB가 설정됩니다.
- Node.js 컨테이너 실행:
- nodejs 컨테이너가 빌드된 후 실행됩니다.
- 환경 변수(COUCHDB_USER, COUCHDB_PASSWORD)를 사용하여 CouchDB와 연결됩니다.
- Node.js 애플리케이션은 CouchDB와 상호작용할 수 있습니다.
- 포트 매핑:
- 3000번 포트를 통해 Node.js 애플리케이션에 접근 가능합니다.
실행 방법
- 파일 저장: docker-compose.yml 파일로 저장합니다.
- 애플리케이션 실행:
- -d 옵션을 추가하면 백그라운드에서 실행됩니다:
docker-compose up -d
- -d 옵션을 추가하면 백그라운드에서 실행됩니다:
- docker-compose up
- 서비스 확인:
- Node.js 애플리케이션: http://localhost:3000
- CouchDB 관리 인터페이스(Futon): http://localhost:5984 (포트 매핑이 정의되지 않았지만 기본 CouchDB 포트는 5984입니다.)
- 컨테이너 중지 및 제거:
- docker-compose down
추가 고려 사항
- 보안:
- COUCHDB_USER, COUCHDB_PASSWORD, FLAG와 같은 민감한 데이터는 .env 파일에 저장하고 docker-compose.yml에서 참조하는 것이 좋습니다.
- 예:
environment: - COUCHDB_USER=${COUCHDB_USER} - COUCHDB_PASSWORD=${COUCHDB_PASSWORD}
- 로그 확인:
- 실행 중 발생하는 문제를 확인하려면 다음 명령어로 로그를 확인하세요:
docker-compose logs
- 실행 중 발생하는 문제를 확인하려면 다음 명령어로 로그를 확인하세요:
- CouchDB 데이터 영구화:
- CouchDB 데이터를 유지하려면 볼륨을 추가해야 합니다:
volumes: - couchdb_data:/opt/couchdb/data
- CouchDB 데이터를 유지하려면 볼륨을 추가해야 합니다:
결론
이 docker-compose.yml 파일은 Node.js 애플리케이션과 CouchDB를 연동하는 간단한 환경을 설정합니다. 서비스 간의 연결, 자동 재시작, 포트 매핑 등이 정의되어 있으며, 환경 변수를 통해 유연하게 구성할 수 있습니다. 추가적인 보안과 영구적인 데이터 저장을 위해 .env 파일과 볼륨을 설정하는 것이 좋습니다.
package.json 파일
{
"name": "app",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "node ./bin/www"
},
"dependencies": {
"axios": "^0.21.1",
"body-parser": "^1.19.0",
"cookie-parser": "~1.4.4",
"dotenv": "^8.2.0",
"ejs": "~2.6.1",
"express": "~4.16.1",
"fs-extra": "^9.1.0",
"http-error": "0.0.6",
"http-errors": "~1.6.3",
"multer": "^1.4.2",
"nano": "^9.0.5",
"nodemon": "^2.0.7",
"request": "^2.88.2",
"requests": "^0.3.0"
}
}
package.json 파일은 Node.js 프로젝트에서 사용되는 패키지 관리 파일로, 프로젝트의 설정 정보와 종속성(dependencies) 정보를 담고 있습니다. 이는 프로젝트의 중심 파일로서, 프로젝트 실행, 의존성 관리, 스크립트 실행 등을 제어합니다.
파일 구조 분석
1. name
"name": "app",
- 프로젝트 이름을 정의합니다.
- 예제에서 프로젝트 이름은 app입니다.
2. version
"version": "0.0.0",
- 프로젝트의 버전을 나타냅니다.
- 버전 관리는 Semantic Versioning(SemVer) 규칙을 따릅니다.
- 형식: MAJOR.MINOR.PATCH
- MAJOR: 비호환 변경
- MINOR: 기능 추가
- PATCH: 버그 수정
- 형식: MAJOR.MINOR.PATCH
3. private
"private": true,
- 프로젝트가 비공개임을 나타냅니다.
- true로 설정하면 이 패키지를 npm publish 명령으로 배포할 수 없습니다. (실수로 배포를 방지)
4. scripts
"scripts": {
"start": "node ./bin/www"
}
- 프로젝트의 명령어 스크립트를 정의합니다.
- 여기서는 npm start 명령을 실행하면 다음 명령이 실행됩니다:
node ./bin/www
- 일반적으로 start 스크립트는 애플리케이션의 시작점을 실행하는 명령을 포함합니다.
5. dependencies
"dependencies": {
"axios": "^0.21.1",
"body-parser": "^1.19.0",
...
}
- 프로젝트에서 사용하는 런타임 의존성(dependencies)을 정의합니다.
- dependencies 항목에 나열된 패키지는 프로젝트를 실행하는 데 필수적입니다.
주요 패키지:
- axios: HTTP 요청을 처리하는 클라이언트 라이브러리.
- body-parser: 요청 본문 데이터를 처리하는 미들웨어.
- cookie-parser: 요청의 쿠키 데이터를 파싱.
- dotenv: .env 파일에 정의된 환경 변수를 로드.
- ejs: 템플릿 엔진으로, HTML 렌더링에 사용.
- express: Node.js를 위한 웹 애플리케이션 프레임워크.
- fs-extra: 파일 시스템 작업을 쉽게 처리하는 라이브러리.
- multer: 파일 업로드를 처리하는 미들웨어.
- nano: CouchDB 클라이언트 라이브러리.
- nodemon: 개발 중 변경 사항을 자동으로 반영하여 서버를 재시작.
- request 및 requests: HTTP 요청 라이브러리 (deprecated이므로 axios 사용 권장).
package.json 파일의 역할
- 프로젝트 정보 관리:
- 프로젝트 이름, 버전, 저작권, 스크립트 등을 정의.
- 의존성 관리:
- 프로젝트에서 사용하는 외부 라이브러리나 프레임워크를 정의.
- npm install 명령으로 dependencies에 정의된 패키지를 설치.
- 스크립트 실행:
- 프로젝트 실행(npm start), 빌드(npm run build), 테스트(npm test) 등 스크립트를 정의하여 자동화.
- 배포 및 협업:
- package.json 파일을 공유하면, 다른 개발자가 동일한 환경에서 프로젝트를 실행할 수 있음.
사용 예시
1. 의존성 설치
- package.json에 정의된 모든 의존성을 설치:
npm install
- 의존성이 node_modules/ 폴더에 설치됩니다.
2. 프로젝트 실행
- start 스크립트를 실행:
npm start
- 실행 결과: node ./bin/www
3. 새로운 의존성 추가
- 새로운 패키지를 추가하고 dependencies에 자동으로 추가:
npm install 패키지이름
요약
- package.json은 Node.js 프로젝트의 필수 파일로, 프로젝트의 정보, 의존성, 스크립트를 관리합니다.
- 예제에서 이 파일은 Express 기반 Node.js 애플리케이션을 설정하며, CouchDB와 연동하기 위해 nano 라이브러리를 사용하는 점이 특징입니다.
- 개발자는 이 파일을 기반으로 프로젝트를 설정하고 동일한 환경에서 실행할 수 있습니다.
app.js 파일
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
const nano = require('nano')(`http://${process.env.COUCHDB_USER}:${process.env.COUCHDB_PASSWORD}@couchdb:5984`);
const users = nano.db.use('users');
var app = express();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
/* GET home page. */
app.get('/', function(req, res, next) {
res.render('index');
});
/* POST auth */
app.post('/auth', function(req, res) {
users.get(req.body.uid, function(err, result) {
if (err) {
console.log(err);
res.send('error');
return;
}
if (result.upw === req.body.upw) {
res.send(`FLAG: ${process.env.FLAG}`);
} else {
res.send('fail');
}
});
});
// catch 404 and forward to error handler
app.use(function(req, res, next) {
next(createError(404));
});
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
이 app.js 파일은 Node.js와 Express 프레임워크를 사용하여 작성된 웹 애플리케이션의 주요 서버 코드입니다. 이 파일은 사용자 인증과 CouchDB 연동을 처리하며, 특히 /auth 라우트를 통해 인증 성공 시 환경 변수로 설정된 플래그(FLAG)를 반환합니다.
주요 구성 요소 분석
1. 모듈 가져오기
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
const nano = require('nano')(`http://${process.env.COUCHDB_USER}:${process.env.COUCHDB_PASSWORD}@couchdb:5984`);
- http-errors:
- HTTP 오류를 생성하고 에러 처리를 돕는 모듈.
- express:
- Node.js에서 서버를 구축하기 위한 웹 애플리케이션 프레임워크.
- path:
- 파일 및 디렉토리 경로를 조작하기 위한 Node.js 기본 모듈.
- cookie-parser:
- HTTP 요청에서 쿠키를 파싱하는 미들웨어.
- nano:
- CouchDB와 상호작용하기 위한 Node.js 클라이언트.
- nano.db.use('users')를 통해 users 데이터베이스를 사용.
2. Express 앱 생성
var app = express();
- Express 애플리케이션 인스턴스를 생성하여 라우팅 및 미들웨어 설정을 처리.
미들웨어 설정
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
- express.json():
- JSON 형식의 요청 본문을 처리.
- express.urlencoded():
- URL-encoded 요청 본문을 파싱.
- cookieParser():
- 쿠키 파싱.
- express.static():
- public 디렉토리를 정적 파일 제공 디렉토리로 설정.
뷰 엔진 설정
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
- 뷰 경로:
- views 디렉토리를 뷰 템플릿 경로로 설정.
- 뷰 엔진:
- **EJS(Embedded JavaScript Templates)**를 뷰 엔진으로 사용.
라우팅
홈 페이지 (GET /)
app.get('/', function(req, res, next) {
res.render('index');
});
- 클라이언트가 / 경로에 접근하면 views/index.ejs 파일을 렌더링.
사용자 인증 (POST /auth)
app.post('/auth', function(req, res) {
users.get(req.body.uid, function(err, result) {
if (err) {
console.log(err);
res.send('error');
return;
}
if (result.upw === req.body.upw) {
res.send(`FLAG: ${process.env.FLAG}`);
} else {
res.send('fail');
}
});
});
- 기능:
- 요청 본문(req.body.uid)의 uid 값을 users 데이터베이스에서 검색.
- uid에 해당하는 데이터가 없으면 error를 반환.
- 검색 결과의 upw(비밀번호)와 요청 본문의 upw를 비교:
- 일치: 환경 변수 FLAG 값을 반환.
- 불일치: fail 반환.
- 사용 예시:
- 요청:
POST /auth HTTP/1.1 Content-Type: application/json { "uid": "testuser", "upw": "password123" }
- 성공 시 응답:
FLAG: DH{**FLAG**}
- 요청:
에러 처리
404 처리
app.use(function(req, res, next) {
next(createError(404));
});
- 존재하지 않는 경로로 요청 시 404 Not Found 에러를 생성.
전역 에러 핸들러
app.use(function(err, req, res, next) {
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
res.status(err.status || 500);
res.render('error');
});
- 애플리케이션 전역에서 발생한 에러를 처리하고 error 템플릿을 렌더링.
- 개발 환경에서는 상세 에러 정보를 출력.
환경 변수 사용
process.env.COUCHDB_USER
process.env.COUCHDB_PASSWORD
process.env.FLAG
- COUCHDB_USER 및 COUCHDB_PASSWORD:
- CouchDB 데이터베이스 인증 정보.
- 환경 변수로 설정하여 보안 강화.
- FLAG:
- 인증 성공 시 반환되는 플래그 값. 보통 민감한 데이터(예: CTF 문제)로 사용.
결론
- 이 app.js 파일은 Express 프레임워크를 사용하여 웹 서버를 구축하고, CouchDB를 연동하여 사용자 인증을 처리합니다.
- /auth 라우트를 통해 사용자 인증을 수행하고, 인증 성공 시 플래그를 반환하는 구조입니다.
- 환경 변수를 사용하여 민감한 정보를 보호하며, 에러 처리와 뷰 렌더링도 포함된 간단한 Node.js 애플리케이션입니다.
이 코드는 주로 CTF 환경, 백엔드 연습, 또는 간단한 사용자 인증 프로젝트에서 사용될 수 있습니다. 😊
로그인 폼에서 받은 값을 처리하는 부분의 코드를 살펴보면
get() 함수를 통해 uid값을 받고 있는데
get() 함수는 전달된 인자에 대해 특수 구성 요소의 포함 여부를 검사하지 않기 때문에 취약점이 있다.
[특수 구성 요소]
/
/_all_dbs
/_utils
/db
/{db}/_all_docs
/{db}/_find
uid에 _all_docs를 입력하면
데이터베이스에 포함된 모든 도큐먼트를 result로 반환하는데,
키 값이 upw인 데이터를 반환하지 않기 때문에
32행에서 비교하고 있는 result.upw는 undefined가 된다.
따라서 req.body.upw를 undefined로 하면
result.upw === req.body.upw가 True가 되어서 FLAG가 출력될 것이다.
출처ㅣ https://studyforall.tistory.com/49
이제 문제를 풀어보자
개발자 도구 colsole 탭에서 documnet.getElementsByName()으로 upw의 값을 출력해보니
undefined가 아니라 객체가 떴다.
req.body.upw를 undefined로 하면
result.upw === req.body.upw가 True가 되어서 FLAG가 출력될 것이다.
true가 아니라 false라서 fail이 뜬거다
오늘의 교훈
중간에 막히면 그냥 통째로 없애자
'Dreamhack > Dreamhack Wargame (Challenge)' 카테고리의 다른 글
[83] IT 비전공자 [dreamhack]BypassIF문제 풀기 (0) | 2024.12.01 |
---|---|
[82] IT 비전공자 [dreamhack]memory_leakage문제 풀기 (0) | 2024.11.30 |
[80] IT 비전공자 [dreamhack]Type c-j문제 풀기 (0) | 2024.11.28 |
[79] IT 비전공자 [dreamhack]out of money문제 풀기 (0) | 2024.11.27 |
[78] IT 비전공자 [dreamhack]Simple Crack Me문제 풀기 (0) | 2024.11.26 |