Regular Expression
오늘의 주제는 정규표현식이다. 처음에는 쉬어가는 주제로 골랐지만... 예상이 틀린 모양이다. 가볍게 목차만 잡아봤는데 생각보다 엄청나게 다룰 것들이 많다. 그래도 내용을 가볍게 훑어 보니 하나같이 영양가 있어 보이는 것들이 많다. 끝까지 최대한 열심히 해서 한번 채워 보자.
정규표현식(Regular Expression, 약칭 RegEx 또는 regexp)은 문자열 내 특정 패턴을 찾거나, 검사하거나, 치환하는 데 사용하는 일종의 패턴 언어이다. 정규표현식은 단순한 문자열 검색을 넘어, 복잡한 규칙 기반의 텍스트 필터링을 가능하게 한다.
이 개념은 1950년대 수학자 스티븐 콜 클레이니(Stephen Cole Kleene) 가 형식 언어 이론에서 ‘정규 언어(Regular Language)’를 정의하면서 비롯되었다. 이후 컴퓨터 과학 분야에서는 Unix 유닉스 운영체제의 grep
, sed
, awk
와 같은 유틸리티에서 도입되었고, 이는 텍스트 기반의 데이터 처리 방식에 혁명적인 영향을 끼쳤다.
정규표현식은 초기에는 컴파일러 이론과 이론적 언어 분석에 국한되어 있었지만, 지금은 보안, 데이터 분석, 웹 개발, 시스템 관리 등 거의 모든 실무 영역에서 필수적인 기술로 자리 잡고 있다.
문자열을 다루는 대부분의 프로그램에서는 특정 패턴을 찾거나, 사용자의 입력을 검증하거나, 민감정보를 추출하거나, 로그 파일에서 의미 있는 정보를 분석해야 할 때가 있다. 이럴 때, 조건문이나 루프만으로 처리하는 것은 너무 복잡하거나 비효율적일 수 있다. 정규표현식은 이러한 문제를 간결하고 강력하게 해결해준다.
간결한 패턴 정의: 복잡한 문자열 조건을 몇 줄의 패턴으로 표현 가능
재사용성과 이식성: 대부분의 언어에서 동일한 정규식을 적용 가능
자동화에 적합: 파일 이름 필터링, 로그 파싱, HTML 데이터 추출 등 반복 작업 자동화에 용이
입력 검증 보안 강화: 이메일 주소, 전화번호, 비밀번호 형식 등을 검증해 XSS, SQL Injection 같은 보안 취약점 방지
정규표현식은 잘만 사용하면 텍스트 기반 자동화 작업에서 수십 배의 생산성을 가져올 수 있다.
정규표현식은 대부분의 프로그래밍 언어와 개발 도구, 보안 플랫폼에서 핵심 기능으로 지원된다. 특히 아래와 같은 환경에서는 거의 필수적으로 사용된다.
re
모듈 사용 (re.search
, re.findall
, re.sub
등)/pattern/
또는 new RegExp()
구문으로 사용java.util.regex
패키지에서 제공System.Text.RegularExpressions
네임스페이스에서 지원도구/환경 | 용도 예시 |
---|---|
grep/sed/awk | 리눅스에서 로그 검색 및 편집 자동화 |
Wireshark | 패킷 내 문자열 탐지 |
IDS/IPS | 정규식 기반 룰로 악성 트래픽 식별 (e.g., Snort) |
YARA | 악성코드 탐지용 패턴 정의 |
SIEM | 로그 분석 및 경고 규칙 정의 |
Suricata | 정규식 기반 서명 탐지 엔진 |
이처럼 정규표현식은 단순한 개발 도구를 넘어 보안 인프라, 데이터 분석 플랫폼, 자동화 파이프라인 등 다양한 분야에 깊이 통합되어 있으며, 이를 정확히 이해하고 활용하는 능력은 실무에서 큰 차이를 만든다.
정규표현식은 특정한 문자열 패턴을 정의하기 위한 문법 체계이다. 기본적인 문법 구조를 이해하면, 텍스트 내에서 원하는 형식을 쉽게 탐색하고 조작할 수 있다.
리터럴(literal) 문자는 그 자체로 해석되는 문자이다. 예를 들어 hello
라는 정규표현식은 문자열 안에서 정확히 "hello"
라는 단어를 찾는다.
하지만 정규표현식에는 특수한 기능을 가진 문자들도 있다. 이들을 특수문자(meta-character) 라고 하며, 정규표현식의 동작을 제어한다.
문자 | 의미 |
---|---|
. | 임의의 한 문자 (개행 제외) |
^ | 문자열의 시작 |
$ | 문자열의 끝 |
* | 앞 문자의 0회 이상 반복 |
+ | 앞 문자의 1회 이상 반복 |
? | 앞 문자의 0회 또는 1회 |
` | ` |
() | 그룹화 |
[] | 문자 집합 |
\ | 이스케이프 문자 (특수문자를 문자 그대로 사용하고 싶을 때) |
[]
안에 문자를 나열하면, 그 중 하나라도 일치하면 매칭된다. 이를 문자 클래스라 한다.
[abc]
→ a
, b
, 또는 c
중 하나와 일치[0-9]
→ 숫자 하나와 일치 (0부터 9까지)[A-Za-z]
→ 모든 영문자 (대소문자 포함)[^abc]
→ a
, b
, c
를 제외한 문자[aeiou]
→ 모음 하나 찾기[^0-9]
→ 숫자가 아닌 문자 찾기반복자(quantifier) 는 특정 패턴이 얼마나 반복되어야 하는지를 지정하는 도구이다.
기호 | 의미 |
---|---|
* | 0회 이상 반복 (zero or more) |
+ | 1회 이상 반복 (one or more) |
? | 0회 또는 1회 (optional) |
{n} | 정확히 n회 반복 |
{n,} | n회 이상 반복 |
{n,m} | n회 이상, m회 이하 반복 |
a*
→ 빈 문자열, "a"
, "aa"
, "aaa"
등a+
→ "a"
, "aa"
, "aaa"
등 (단, 최소 1회)a{2,4}
→ "aa"
, "aaa"
, "aaaa"
만 일치앵커(anchor) 는 문자열 내에서 위치를 지정한다. 문자 자체가 아니라 위치 조건이다.
앵커 | 의미 |
---|---|
^ | 문자열의 시작 |
$ | 문자열의 끝 |
^abc
→ "abc"
로 시작하는 문자열xyz$
→ "xyz"
로 끝나는 문자열^abc$
→ 전체 문자열이 정확히 "abc"
여야 매칭.
(점) 은 개행 문자를 제외한 임의의 문자 하나를 의미한다.
a.c
→ "abc"
, "a9c"
, "a_c"
등과 매칭주의:
.
은 빈 문자는 매칭하지 않으며, 기본 설정에서는 개행 문자(\n
)도 매칭하지 않는다.
정규표현식에서 특수문자(.
, *
, +
, ?
등)를 문자 그대로 사용하고 싶을 때는 이스케이프 문자 \
를 사용한다.
\.
→ 실제 점 문자 .
매칭\d
→ 숫자 (0-9)\w
→ 알파벳, 숫자, 밑줄 (word character)\s
→ 공백 문자 (스페이스, 탭 등)정규표현식 자체에는 기본적으로 주석 기능이 없지만, 일부 언어 또는 플래그에서는 x
플래그를 통해 공백과 주석을 허용할 수 있다.
pattern = re.compile(r"""
^ # 문자열의 시작
[A-Z][a-z]+ # 대문자로 시작하고, 소문자 여러 개
\s # 공백 문자
[A-Z][a-z]+ # 성
$ # 문자열의 끝
""", re.VERBOSE)
정규표현식에서 ()
는 단순히 여러 문자를 묶는 것(grouping) 을 넘어서, 해당 패턴에 매칭된 문자열을 "기억(capture)" 하게 만든다. 이 기능은 백레퍼런스, 치환, 추출 등에 매우 유용하게 사용된다.
정규표현식에서 ()
는 패턴을 논리적으로 묶어 하나의 단위로 처리할 수 있게 해준다.
(ab)+
"abab"
, "ab"
, "ababab"
또한 괄호로 묶인 부분은 자동으로 "그룹 번호"가 부여되며, 후속 처리나 참조에 사용된다.
그룹 번호 | 의미 |
---|---|
\1 또는 $1 | 첫 번째 괄호 그룹 |
\2 , $2 | 두 번째 그룹 |
… | … |
그룹 번호는 괄호가 열리는 순서대로 매겨진다.
기본 괄호 ()
는 캡처를 수행하지만, 모든 그룹이 반드시 "기억되어야" 하는 것은 아니다. 단순히 논리적 묶음만 하고, 캡처를 원치 않는 경우에는 (?:...)
를 사용한다.
(?:http|https)://
표현식 | 그룹으로 기억? | 사용 예 |
---|---|---|
(abc) | O (캡처 그룹) | \1 , $1 등으로 참조 가능 |
(?:abc) | X (비캡처 그룹) | 참조하지 않고 그룹화만 |
비캡처 그룹은 성능상 이점이 있으며, 캡처가 불필요할 경우 반드시 사용하는 것이 좋다.
|
는 OR 연산자, 즉 대체(alternation) 를 의미한다. 왼쪽 또는 오른쪽 패턴 중 하나라도 일치하면 매칭된다.
정규표현식: cat|dog
"cat"
또는 "dog"
와 매칭정규표현식: (cat|dog)s?
"cat"
, "cats"
, "dog"
, "dogs"
모두 매칭됨주의:
|
는 가장 가까운 괄호나 표현 범위까지만 적용되므로, 우선순위 제어를 위해 괄호를 꼭 사용하는 것이 좋다.
http|https://
"http"
또는 "https://"
를 찾는다 (의도한 대로 작동하지 않음)(http|https)://
"http://"
또는 "https://"
를 정확히 탐지정규표현식의 강력한 기능 중 하나가 백레퍼런스(backreference) 이다. 앞서 매칭된 캡처 그룹의 값과 같은 값을 나중에 다시 참조할 수 있게 한다.
표현 언어 | 백레퍼런스 표현 |
---|---|
정규표현식 내부 | \1 , \2 등 |
Python, Perl 등 | match.group(1) |
치환(Replacement) | $1 , $2 등으로 표현되는 경우도 있음 |
\b(\w+)\s+\1\b
"hello hello"
)\1
: 첫 번째 단어가 다시 나와야 매칭됨<(\w+)>.*?</\1>
(\w+)
: 태그명 캡처</\1>
: 동일한 태그명으로 닫힘HTML과 같은 중첩 구조를 완벽히 다루기는 어렵지만, 간단한 태그 구조에서는 매우 유용하다.
Lookaround는 어떤 조건을 만족하는 "앞"이나 "뒤"의 텍스트를 검사하되, 실제 매칭에는 포함하지 않는 기능이다. 매우 강력하면서도 실전에서 자주 쓰인다.
패턴 뒤에 오는 내용을 조건으로 지정.
(?=...)
: 긍정 전방 탐색 (positive lookahead)(?!...)
: 부정 전방 탐색 (negative lookahead)\w+(?=@gmail\.com)
@gmail.com
앞에 있는 단어만 매칭 (이메일 주소에서 사용자명 추출)foo(?!bar)
bar
가 오지 않는 foo
만 매칭패턴 앞에 오는 내용을 조건으로 지정.
(?<=...)
: 긍정 후방 탐색 (positive lookbehind)(?<!...)
: 부정 후방 탐색 (negative lookbehind)(?<=\$)\d+
$
기호 바로 뒤에 오는 숫자만 매칭 (가격 탐지 등)(?<!https:)//\w+
https:
가 아닌 경우에만 //
뒤의 문자열 매칭일부 언어(Python, JavaScript 등)에서는 후방 탐색에 고정 길이만 허용하는 제약이 있으니 사용 전 확인 필요.
조건부 표현식은 이전에 캡처된 그룹이 존재하는지에 따라 패턴을 분기한다. 주로 복잡한 조건문 같은 처리가 필요한 경우 유용하다.
(?(group_number) then_pattern | else_pattern)
(<)?\w+(?(1)>)
<word>
또는 word
형태를 모두 처리<
가 있으면, >
도 있어야 함실무에서 자주 쓰이진 않지만, XML/HTML 대응 구조 체크 등 특정 구조에서 유용하게 사용 가능함.
정규표현식에서 반복자(*
, +
, {n,m}
등)는 기본적으로 Greedy(탐욕적) 하게 동작한다. 즉, 가능한 많은 문자를 매칭하려 한다. 이에 반해, Lazy(게으른) 매칭은 가능한 한 적은 양을 매칭한다.
반복자 | 의미 | 예시 |
---|---|---|
* | 0회 이상 (Greedy) | a.*b → "accccb" 전체 매칭 |
*? | 0회 이상 (Lazy) | a.*?b → "accccb" 중 "acccb" 까지 |
<.*>
<div><p>text</p></div>
전체를 한 번에 매칭<.*?>
<div>
, <p>
등 태그 하나씩 각각 매칭Lazy 모드를 활용하면 불필요한 과한 매칭 방지가 가능하므로, HTML, JSON 등 중첩 구조에서 매우 중요함.
정규표현식은 텍스트의 라인 구분 방식이나 개행 문자 처리 방식에 따라 동작 방식이 달라질 수 있다. 이 때 사용하는 것이 멀티라인 모드(m) 와 Dotall 모드(s) 이다.
m
)^
, $
가 문서 전체가 아니라 각 줄의 시작/끝으로 작동하게 함^Error
"Error"
를 찾음"Error"
를 찾음사용 예: 로그 파일처럼 줄 단위로 분석할 때 필수
s
).
문자가 개행(\n
)을 제외하고 모든 문자와 매칭됨a.*b
a
부터 b
까지, 같은 줄에서만 매칭a
부터 b
까지 매칭 가능import re
re.findall(r"^Error", log, flags=re.MULTILINE)
re.findall(r"a.*b", text, flags=re.DOTALL)
대부분의 정규표현식 엔진(PCRE, Python, Java 등)은
(?m)
또는(?s)
등으로 인라인으로도 설정 가능함
정규표현식은 웹 보안에서 입력값 검증과 공격 탐지에 널리 사용된다. 가장 대표적인 것이 XSS(크로스사이트 스크립팅) 와 SQL 인젝션 필터링이다.
<.*?(script|img|svg|iframe|on\w+)[^>]*>
<script>
, <img>
등의 위험 태그, onload
, onclick
등 이벤트 속성 탐지(?:javascript|vbscript|data):[^\s]+
정규표현식만으로 완전한 XSS 방지는 불가능하므로, WAF와 CSP와의 병행이 필수이다.
(?:\%27)|(?:')|(?:--)|(/\*)|(\*/)|(\b(select|union|insert|drop|update|delete)\b)
\b(OR|AND)\b\s+\d+=\d+
고급 탐지에서는 정규표현식 외에 파라미터 분석, 행동 기반 탐지와 함께 사용된다.
정규표현식은 보안 로그에서 이상 행위나 오류를 빠르게 필터링하는 데 핵심 도구로 쓰인다.
"(GET|POST|HEAD)\s+(/[^\s]*)\s+HTTP/\d\.\d"\s+(4\d{2}|5\d{2})
Failed password for (invalid user )?\w+ from (\d{1,3}\.){3}\d{1,3}
sshd
로그에서 로그인 실패 항목만 추출보안 필터나 스크립트 작성 시, 표준 구조를 가진 데이터들을 인식하고 파싱하는 용도로 자주 사용된다.
[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+
https?:\/\/[\w.-]+(?:\.[\w.-]+)+(?:\/[\w\-.~:/?#[\]@!$&'()*+,;=]*)?
\b(?:\d{1,3}\.){3}\d{1,3}\b
\b[a-fA-F0-9]{32}\b # MD5
\b[a-fA-F0-9]{40}\b # SHA1
\b[a-fA-F0-9]{64}\b # SHA256
YARA에서는 문자열 패턴 탐지에 정규표현식을 직접 사용할 수 있음. 특히, 악성 코드 내 코드 조각, 함수 이름, 특정 문자열 시그니처를 탐지할 때 유용하다.
rule Suspicious_API_Call
{
strings:
$re = /Create(Remote)?Thread/
condition:
$re
}
CreateThread
, CreateRemoteThread
) 탐지rule Encoded_Email_Exfiltration
{
strings:
$email = /[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}/ nocase
condition:
$email
}
YARA는 Perl Compatible RegEx(PCRE)를 지원하며, 복잡한 탐지를 위해서는 조합적 조건 구성이 중요하다.
대부분의 보안 플랫폼은 탐지 정책이나 룰셋 구성 시 정규표현식 기반의 필터링 규칙을 허용한다.
rex
명령어 사용index=web_logs | rex field=_raw "GET\s+(?<url_path>\/\S+)\s+HTTP"
match => { "message" => "%{IP:client} - - \[%{HTTPDATE:timestamp}\] \"%{WORD:method} %{URIPATHPARAM:uri}" }
powershell.+(download|invoke)
포함 여부 필터링cmd\.exe\s+/c\s+net\s+user
같은 고전적 권한 탈취 행위 탐지정규표현식을 개발하고 실험할 때 가장 먼저 활용되는 것이 온라인 테스트 툴이다. 주요 기능은 다음과 같다:
플랫폼 | 특징 |
---|---|
regex101.com | 가장 널리 사용됨. 실시간 매칭 확인, 표현식 해석, 오류 메시지 제공, 여러 언어 모드(Python, JS, PHP 등) 지원 |
regexr.com | 시각화에 강함. 예제 내장, 문법 팁, 문서 연결 탁월 |
regexplanet.com | 다양한 언어별 정규식 테스트 가능 (Java, Python, C#, Ruby 등) |
regexstorm.net | .NET 정규식 전용. Lookbehind 등 지원 여부 확인에 유리 |
re
모듈 사용. re.compile()
로 사전 컴파일 후 .search()
나 .match()
로 테스트 가능.import re
pattern = re.compile(r"\b\d{3}-\d{4}\b")
match = pattern.search("전화번호는 010-1234입니다.")
print(match) # None (패턴 불일치)
pattern.findall(text)
로 매칭되는 모든 항목 출력re.DEBUG
플래그로 컴파일 로그 출력 가능re.compile(r"\d+", re.DEBUG)
RegExp
객체 사용. test()
, match()
, exec()
등을 활용let pattern = /\b\d{3}-\d{4}\b/;
console.log(pattern.test("전화번호는 010-1234입니다.")); // true
matchAll()
로 모든 그룹 추출 가능Pattern
과 Matcher
클래스 사용Pattern pattern = Pattern.compile("\\b\\d{3}-\\d{4}\\b");
Matcher matcher = pattern.matcher("전화번호는 010-1234입니다.");
System.out.println(matcher.find()); // true
matcher.group()
으로 캡처된 결과 확인언어 | 주의사항 |
---|---|
Python | (?<=...) 등 Lookbehind 사용 가능, re.DEBUG 유용 |
JavaScript | Lookbehind는 일부 환경에서만 지원 |
Java | 일부 고급 정규식 미지원, 그룹 추출은 matcher.group(n) |
C# (.NET) | 가장 강력한 정규식 지원, 조건부 패턴까지 가능 |
오류 메시지 예시 | 원인 | 해결 방법 |
---|---|---|
nothing to repeat | * , + 앞에 올 표현식 없음 | a* 가 아니라 *a 처럼 잘못 작성된 경우 |
unterminated character class | 닫는 ] 누락 | [A-Z → [A-Z] |
lookbehind assertion is not fixed width | Lookbehind 길이가 가변적임 | (?<=a+) → 불가. (?<=abc) 처럼 고정 길이여야 함 |
invalid escape sequence | 백슬래시 두 개 필요 (\\ ) | Python에서 "\d" 는 에러 → "\\d" 또는 r"\d" 사용 |
stack overflow or catastrophic backtracking | 과도한 그룹/반복으로 인해 백트래킹 폭주 | 불필요한 반복 제거, Lazy 사용 고려 (+? , *? ) |
(?x)
를 사용해 정규식에 주석 삽입 가능pattern = re.compile(r'''
^ # 시작
[a-zA-Z0-9._%+-]+ # 사용자 이름
@ # @ 기호
[a-zA-Z0-9.-]+ # 도메인
\.[a-zA-Z]{2,}$ # 최상위 도메인
''', re.VERBOSE)
정규식의 성능 문제는 대체로 정규식 엔진이 사용하는 NFA(Non-deterministic Finite Automaton) 와 DFA(Deterministic Finite Automaton) 모델에 따라 달라진다. 이 두 모델은 정규식의 매칭 과정에서 중요한 차이점을 가지고 있다.
특징: NFA는 여러 상태를 동시에 추적할 수 있기 때문에, 다양한 경로를 동시에 탐색한다. 이 방식은 그 자체로 매우 유연하지만, 성능 문제를 일으킬 수 있다.
성능 문제: 복잡한 정규식을 사용할 경우, 상태 전환이 과도하게 발생하여, 특히 백트래킹(backtracking) 이 많이 일어날 수 있다. 백트래킹은 시간 복잡도가 증가하게 하여 성능에 악영향을 미친다.
예시: .*a.*b.*c
와 같은 패턴은 NFA에서 백트래킹을 발생시키기 쉽다.
특징: DFA는 단일 경로만 추적하며, 상태 전환이 고정적이므로 매우 빠르다. DFA는 매칭할 때 한 번에 유일한 경로를 선택하고, 백트래킹이 필요 없기 때문에 성능 면에서 유리하다.
성능 장점: 정규식이 단순하고, 미리 컴파일된 상태에서 매우 빠른 속도를 보인다.
예시: a.*b.*c
와 같은 패턴은 DFA에서 효율적으로 처리된다.
정규식을 작성할 때 성능 문제를 고려해야 할 필요가 있으며, 특히 긴 문자열이나 복잡한 정규식에서 NFA가 성능 저하를 일으킬 수 있다는 점을 유념해야 한다. DFA 방식은 빠르지만, 일부 복잡한 정규식에서는 상태 전환 테이블 크기가 커져 메모리 사용량이 증가할 수 있다.
복잡한 정규식은 가독성뿐만 아니라 성능에도 영향을 미친다. 따라서 정규식을 효율적으로 리팩토링하는 것이 중요하다.
복잡한 정규식에서 동일한 문자열 패턴이 반복될 경우, 이를 별도의 그룹으로 묶어서 중복을 최소화할 수 있다. 예를 들어, (\d{3}-\d{3}-\d{4})|(\d{4}-\d{3}-\d{4})
와 같은 패턴은 (\d{3,4}-\d{3,4}-\d{4})
로 리팩토링할 수 있다.
중복을 없애면 정규식이 간결해지고, 처리 시간이 줄어든다.
단일 문자와 문자 집합 최적화: [^a-zA-Z0-9]
와 같은 문자 집합을 사용하는 대신, 단순한 \W
(모든 비단어 문자)로 교체하는 것이 성능을 개선할 수 있다.
반복자 사용 최소화: *
또는 +
와 같은 반복자는 성능 저하를 초래할 수 있다. 이러한 기호는 조건을 추가하거나, 더 구체적인 패턴으로 바꿔 성능을 개선할 수 있다.
정규식을 클러스터링하여 패턴의 부분을 재사용할 수 있다. 예를 들어, (\d{2}){3}
대신 (\d{2}){3,3}
처럼 고정된 반복 범위를 지정하면 더 효율적으로 동작할 수 있다.
정규식의 가독성을 높이는 것은 유지보수와 협업에 중요한 요소이다. 이를 위해서는 코드 내에서 정규식 자체를 명확하고 이해하기 쉽게 작성해야 한다.
코드 내에서 정규식을 사용할 때는 정규식의 목적이나 동작을 주석으로 설명하는 것이 중요하다.
예를 들어, ^(?:\d{3}-\d{2}-\d{4})$
와 같은 복잡한 정규식에 대해서는 "SSN 번호 형식"과 같은 설명을 덧붙이면 추후 코드 작성자나 리뷰어가 쉽게 이해할 수 있다.
정규식을 변수로 선언하여 의미를 부여할 수 있다. 예를 들어, 전화번호를 매칭하는 정규식을 phonePattern = r"\d{3}-\d{3}-\d{4}"
로 정의하고, 이를 코드 내에서 사용하면 읽기가 쉬워진다.
형식에 따라 변수화: 여러 형식을 처리할 때 동일한 패턴을 변수로 선언해두고 필요할 때 호출하는 방식도 유용하다.
정규식을 함수로 묶어 복잡한 처리 로직을 분리할 수 있다. 예를 들어, 이메일을 검사하는 정규식을 함수로 정의하여 재사용하면 코드가 깔끔해진다.
공격자는 종종 보안 시스템을 우회하기 위해 정규식을 우회하는 특수한 문자열을 사용한다. 이를 방어하는 정규식을 설계하는 것이 중요하다.
XSS 공격을 방어하기 위해 정규식을 사용할 때는 </script>
, <script>
와 같은 문자열을 차단해야 한다. 그러나 공격자는 <ScRiPt>
와 같은 방식으로 대소문자를 섞을 수 있기 때문에 이를 고려한 정규식이 필요하다.
따라서 (?i)<script>
와 같이 대소문자를 무시하는 옵션을 사용해야 한다.
SQL Injection 공격에서 공격자는 '
또는 --
와 같은 특수 문자를 사용하여 쿼리를 변경하려 한다. 이를 방어하는 정규식에서는 입력값에서 이러한 특수 문자를 차단하는 패턴을 사용해야 한다.
예를 들어, [\s;--'"]
와 같은 패턴을 사용하여 공백, 세미콜론, 작은따옴표 등을 차단하는 방식이다.
공격자는 공백을 URL 인코딩하거나 여러 개의 <
문자를 연속해서 사용하여 패턴을 우회하려고 할 수 있다. 이를 방어하려면 정규식에서 문자열 길이나 패턴의 반복을 제한하는 등의 방어 기법을 적용해야 한다.
정규식 엔진은 사용하는 플랫폼과 언어에 따라 특성이 다르다. 특히, PCRE(Perl Compatible Regular Expressions), JavaScript, Python, POSIX는 정규식을 처리하는 방식에서 주요한 차이점들이 존재한다.
PCRE는 Perl 언어에서 사용된 정규식 규칙을 따르며, 여러 프로그래밍 언어와 툴에서 널리 사용된다. 주로 다음과 같은 특징이 있다:
JavaScript에서의 정규식은 ECMAScript 규격에 기반을 두고 있으며, 주요 특징은 다음과 같다:
RegExp
객체를 사용하여 정규식을 처리하며, exec()
와 test()
메서드가 주로 사용된다.Python의 정규식은 re
모듈을 통해 제공되며, 고급 정규식 기능을 지원한다. 주요 특징은 다음과 같다:
re.compile()
을 사용해 정규식을 미리 컴파일할 수 있으며, 패턴 객체를 재사용할 수 있어 성능 향상에 유리하다.re.DEBUG
와 같은 디버깅 옵션을 통해 정규식의 작동 과정을 추적할 수 있다.POSIX는 유닉스 계열 시스템에서 사용하는 정규식 표준이다. 특징은 다음과 같다:
Basic Regular Expression (BRE)
과 Extended Regular Expression (ERE)
두 가지 모드로 나누어진다.특징 | PCRE | JavaScript | Python | POSIX |
---|---|---|---|---|
기능 | 고급 기능 지원 (Lookahead, Lookbehind 등) | 제한적인 기능 (조건부, Lookbehind 미지원) | 고급 기능 지원 (Lookahead, Lookbehind 등) | 기본적인 기능만 지원 |
문법 | Perl 스타일 | ECMAScript 스타일 | Python 스타일 | POSIX 스타일 |
사용처 | Perl, PHP, 다양한 툴 | 웹 브라우저, Node.js | Python | Unix/Linux 시스템 |
호환성 | 고급 기능을 지원하는 다양한 플랫폼 | 일부 브라우저에서 제한적 지원 | Python 환경 내에서 안정적 지원 | Unix 시스템에서 널리 사용됨 |
정규식을 다루는 언어마다 제공하는 기능이 다르며, 이로 인해 발생하는 호환성 문제도 존재한다. 이러한 문제를 해결하기 위해서는 각 언어가 제공하는 정규식 엔진의 차이를 이해하고, 그것에 맞게 정규식을 작성해야 한다.
Lookbehind: JavaScript는 Lookbehind를 지원하지 않지만, Python과 PCRE에서는 Lookbehind를 사용할 수 있다. 이는 일부 복잡한 패턴을 다룰 때 큰 차이를 만든다.
예시:
(?<=\d{3})abc
(3자리 숫자 뒤에 오는 "abc"를 매칭)조건부 표현식: POSIX와 JavaScript에서는 조건부 표현식을 사용할 수 없다. 이에 비해 Python과 PCRE는 조건부 표현식을 지원하여, 패턴 내에서 더 복잡한 논리적 분기를 구현할 수 있다.
호환성 이슈: 동일한 정규식이 서로 다른 엔진에서 다르게 동작할 수 있다. 예를 들어, Python에서 동작하는 정규식이 POSIX 환경에서는 동작하지 않을 수 있다. 이 경우, 정규식을 다시 작성하거나 특정 엔진에 맞는 형태로 변환해야 한다.
고급 기능의 제한: 일부 고급 기능(예: 조건부 표현식, Lookbehind 등)은 모든 언어에서 지원되지 않기 때문에, 크로스 플랫폼에서의 호환성 문제가 발생할 수 있다. 이를 해결하기 위해서는 각 언어에 맞는 대체 패턴을 찾아야 한다.
각 플랫폼에서 제공하는 정규식 엔진은 고유한 특성을 가지며, 이에 따라 성능이나 기능에 차이가 있을 수 있다. 이는 정규식을 작성할 때 중요한 요소가 된다.
re.DEBUG
와 같은 디버깅 기능을 통해 코드 최적화가 용이하다.정규식은 이메일 주소, URL, IP 주소와 같은 텍스트를 추출하거나 유효성을 검사하는 데 널리 사용된다.
이메일 주소는 사용자 이름과 도메인 이름으로 구성된다. 이 패턴은 사용자 이름과 도메인 이름의 구문을 검증하기 위해 사용된다.
^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$
^[a-zA-Z0-9._%+-]+
: 이메일의 사용자 이름을 검증 (영문, 숫자, 특수기호 허용)@[a-zA-Z0-9.-]+
: @
기호 이후 도메인 이름\.[a-zA-Z]{2,}$
: 도메인의 최상위 도메인(TLD) 부분도메인 이름을 검증할 때 유용한 정규식이다. 도메인 이름은 알파벳과 숫자, 하이픈으로 구성되며, 끝은 최상위 도메인(TLD)이어야 한다.
^[a-zA-Z0-9-]+\.[a-zA-Z]{2,}$
^[a-zA-Z0-9-]+
: 도메인 이름의 첫 부분 (알파벳, 숫자, 하이픈 허용)\.[a-zA-Z]{2,}$
: 마침표 이후의 최상위 도메인 (2자 이상)URL의 유효성을 검사하는 정규식이다. HTTP, HTTPS, FTP 등의 다양한 프로토콜을 포함하여 URL을 검사할 수 있다.
^(https?|ftp):\/\/[^\s/$.?#].[^\s]*$
^(https?|ftp)
: HTTP 또는 FTP 프로토콜:\/\/
: ://
구분 기호[^\s/$.?#].[^\s]*$
: URL 경로 및 쿼리 문자열 부분 (공백, /
, ?
, #
등의 특수 문자 제외)IPv4 주소를 검증하기 위한 정규식이다. IPv4는 네 개의 0~255 범위의 숫자로 이루어진다.
^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$
설명:
25[0-5]
: 250~255 범위의 숫자2[0-4][0-9]
: 200~249 범위의 숫자[01]?[0-9][0-9]?
: 0~199 범위의 숫자이 정규식은 IPv4 주소 형식을 정확하게 검증할 수 있다.
정규식은 파일 경로, 버전 문자열, 해시 값 등 다양한 시스템 정보를 검증하는 데 유용하게 사용된다.
파일 경로는 운영 체제에 따라 다를 수 있다. 윈도우와 유닉스 계열 시스템에서 파일 경로 형식이 다르기 때문에 이를 고려한 정규식이 필요하다.
^(/[^/ ]*)+/?$
/[^/ ]*
: 각 디렉토리 이름이 공백이나 /
가 아닌 문자열로 구성/?$
: 마지막에는 슬래시(/
)가 올 수 있고, 끝날 수 있음버전 문자열은 보통 "주버전.부버전.수정버전" 형식이다. 예를 들어, 1.0.0
, 2.1.0
등이 있다.
^\d+\.\d+\.\d+$
\d+
: 숫자 1개 이상\.
: 점 (버전 구분자)1.0.0
과 같은 간단한 버전 번호 형식을 검증할 수 있다.MD5, SHA-1, SHA-256과 같은 해시 값은 고정된 길이의 16진수 문자열이다. 각 해시 알고리즘에 대한 패턴을 정의할 수 있다.
^[a-f0-9]{32}$
[a-f0-9]{32}
: 16진수 값이 32자리정규식을 활용하여 개인정보를 추출하거나 검증할 수 있다. 예를 들어, 주민등록번호나 전화번호는 국가별로 형식이 다르지만, 기본적인 구조는 고정적이다.
한국의 주민등록번호는 6자리 숫자-7자리 숫자
형식
^\d{6}-\d{7}$
\d{6}
: 6자리 숫자 (생년월일)-\d{7}
: 하이픈 뒤 7자리 숫자전화번호는 국가별로 형식이 다르지만, 일반적인 형태로 검증할 수 있다.
^\d{2,3}-\d{3,4}-\d{4}$
\d{2,3}
: 2자리 또는 3자리 숫자 (지역 번호)-\d{3,4}-\d{4}
: 하이픈 구분을 포함한 7자리 또는 8자리 숫자로그 파일에서 특정 패턴을 추출하거나 필터링할 때 자주 사용되는 정규식 예제를 소개한다.
서버 로그에서 IP 주소를 추출할 때 유용하다. 일반적으로 각 로그 항목에는 요청을 보낸 클라이언트의 IP 주소가 포함된다.
^(\d{1,3}\.){3}\d{1,3}
(\d{1,3}\.)
: 1~3자리 숫자와 마침표{3}
: 3번 반복 (IPv4 주소의 각 부분)\d{1,3}
: 마지막 1~3자리 숫자서버 로그에는 보통 요청이 발생한 시간이 포함된다. 로그에서 시간을 추출하기 위한 정규식이다.
\[(\d{2}/[A-Za-z]+/\d{4}:\d{2}:\d{2}:\d{2} \+\d{4})\]
\[(\d{2}/[A-Za-z]+/\d{4}:\d{2}:\d{2}:\d{2} \+\d{4})\]
: 대괄호 안에 있는 날짜와 시간정규표현식은 보안 분야에서 중요한 역할을 하며, 로그 분석, 입력 검증, 악성 코드 탐지 등의 작업에 널리 사용된다. 그러나 정규표현식이 제대로 사용되지 않으면 보안상 취약점이 발생할 수 있으며, 특히 ReDoS 공격과 같은 위험을 초래할 수 있다.
ReDoS 공격은 정규표현식 서비스 거부 공격으로, 정규표현식의 비효율적인 설계로 인해 시스템 성능을 저하시켜 서비스 거부 상태를 초래하는 공격이다. 이는 주로 정규표현식이 catastrophic backtracking 문제에 취약할 때 발생한다. 악의적인 사용자는 고의로 입력값을 조작하여 정규표현식 엔진이 지나치게 많은 시간을 소모하도록 할 수 있다.
정규표현식 엔진은 텍스트와 패턴을 매칭할 때 각 경우의 수를 탐색한다. 복잡한 정규표현식이나 비효율적인 반복문을 사용하면, 특히 반복 연산자(*
, +
, ?
, {m,n}
등)와 분리된 패턴들이 서로 맞물릴 때 계산 시간이 급격히 증가할 수 있다. 이러한 현상은 백트래킹(backtracking) 이라고 하며, 비효율적인 정규표현식은 수천, 수백만 번의 백트래킹을 유발할 수 있다.
^(a|aa)+$
입력값: "aaaaaaaaaaaaa..."
(길이가 매우 긴 문자열)
이 정규표현식은 a
또는 aa
의 반복을 허용한다. 긴 입력값이 주어졌을 때, 정규표현식 엔진은 가능한 모든 방법으로 입력값을 분할하여 매칭을 시도한다. 이로 인해 catastrophic backtracking이 발생하며, 시스템 자원을 과도하게 소모하게 된다.
*
, +
, ?
)을 최소화하고, 가능하면 명확하게 범위를 지정하는 것이 좋다.regex101
같은 도구에서 성능을 테스트하고, 백트래킹이 발생할 가능성이 높은 패턴을 점검할 수 있다.입력값 검증은 보안에서 중요한 부분이며, 악의적인 사용자로부터 시스템을 보호하기 위해 필수적이다. 정규표현식을 활용한 입력값 검증은 두 가지 주요 접근 방식으로 나눠볼 수 있다.
입력값 검증은 모든 입력을 검증하여 허용되지 않는 데이터를 필터링하는 방식이다. 정규표현식을 사용하여 예상되는 형식만을 허용하고, 그 외의 데이터를 거부할 수 있다. 예를 들어, 이메일 주소, 전화번호, 주민등록번호 등의 입력 형식을 검증할 때 사용된다.
장점:
단점:
허용 목록 접근 방식은 특정 조건을 만족하는 정확히 정의된 입력만 허용하는 방식이다. 이는 입력값 검증과는 반대로, 허용된 항목만을 명시적으로 정의하는 것이다.
장점:
단점:
보안적으로 가장 강력한 방법은 허용 목록 접근 방식을 사용하는 것이다. 하지만 현실적으로 입력값 검증을 통해 허용되는 형식을 명확히 정의하는 것이 더 직관적이고 구현이 쉬운 경우가 많다. 두 접근 방식은 보안 요구 사항에 맞춰 적절히 결합하여 사용하는 것이 좋다.
보안 룰 작성 시 정규표현식은 시스템의 무결성, 데이터 유효성, 악성 코드 탐지 등을 위해 매우 중요하다. 보안 시스템에서는 정규표현식을 사용하여 악성 패턴을 감지하고, 공격을 차단하는 데 활용한다.
WAF는 웹 애플리케이션에서 발생할 수 있는 다양한 공격 (SQL Injection, XSS 등)을 방어하는 역할을 한다. 이때, 정규표현식을 사용하여 악성 요청 패턴을 탐지하고, 차단할 수 있다.
(<script>)
, ' OR 1=1 --
와 같은 SQL 인젝션 또는 XSS 공격 패턴을 정규표현식으로 검출정규표현식은 로그 분석 및 침입 탐지 시스템(IDS) 에서도 핵심적인 역할을 한다. 특정 패턴이나 키워드를 탐지하여 공격 징후를 추적하고, 알림을 발생시킨다.
failed login attempt
와 같은 패턴을 탐지하거나, 악성 스크립트가 포함된 요청을 차단정규표현식은 악성 코드 탐지에도 사용된다. 예를 들어, YARA나 Suricata와 같은 도구에서는 정규표현식을 사용하여 특정 파일 서명이나 패턴을 탐지한다.
정규표현식은 특정 바이러스나 악성 파일의 서명을 추적하는 데 유용하다. 악성 코드의 특정 문자열이나 특정 동작을 정의할 수 있다.
정규표현식은 다양한 보안 도구에서 악성 코드 탐지 및 침입 탐지의 중요한 도구로 활용된다. 여기에서는 YARA, Suricata, Sigma와 같은 보안 툴에서 정규표현식을 어떻게 적용하는지 살펴본다.
YARA는 파일이나 프로세스에서 특정 악성 코드 서명이나 패턴을 검출하는 데 사용된다. YARA 룰에서 정규표현식을 사용하여 바이러스나 악성 코드의 특성을 정의할 수 있다.
rule MalwareExample
{
strings:
$malware_pattern = /bad_pattern_of_malware/i
condition:
$malware_pattern
}
YARA는 정규표현식을 통해 특정 문자열을 검색하여 악성 코드 또는 의심스러운 파일을 탐지한다.
Suricata는 고급 침입 탐지 시스템(IDS) 및 침입 방지 시스템(IPS)으로, 네트워크 트래픽을 분석하고 악성 활동을 탐지한다. Suricata는 정규표현식을 활용하여 네트워크 트래픽에서 악성 패턴을 탐지할 수 있다.
alert http any any -> any any (msg:"SQL Injection attempt"; content:"' OR 1=1 --"; classtype:attempted-user; sid:1000001;)
Suricata는 content
에 정규표현식을 사용할 수 있으며, 이를 통해 SQL 인젝션, XSS 등 여러 종류의 공격을 탐지할 수 있다.
Sigma는 다양한 보안 정보 및 이벤트 관리(SIEM) 시스템에 대한 공통된 로그 기반 탐지 규칙을 작성하는 프레임워크이다. Sigma에서는 정규표현식을 사용하여 로그에서 특정 이벤트나 패턴을 식별할 수 있다.
title: Potential SQL Injection
logsource:
product: windows
detection:
selection:
EventID: 1234
Query: /' OR 1=1 --
Sigma 규칙에서 정규표현식은 로그 필터링 및 패턴 매칭에 사용된다. 이를 통해 악성 코드 또는 공격을 추적하고 탐지할 수 있다.
역대급으로 힘들었던 주제였다. 내용도 내용이지만, 실제 플랫폼/툴에서 덩규표현식이 사용되는 예시를 하나하나 찾기 힘들어 gpt를 이용해 예시를 받았는데, 또 그걸 이해한다고 시간을 쓰다 보니 초기에 상정한 시간에 비해 한참 더 걸렸다.
그래도 그만큼 배운 것들이 많긴 하지만... 앞으로도 daily라는 작성 기조를 이어나가려면 조금은 템포 조절을 해야 할 것 같다는 생각이 든다. 다음주 주제는 조금 가벼운 걸로 잡아봐야겠다.