Dec 28, 2021 - Apache Log4j 취약점 진단

취약점 요약

Apache Log4j 2.17.0 이전 버전은 LDAP JNDI 파서를 통해 원격 코드 실행 취약점이 발생합니다.
configuration, log message, parameter에 사용되는 Apache Log4j 2.0 Beta9 ~ 2.16.0 사이에 해당하는 모든 버전의
Log4j 2 JNDI 기능은 공격자가 LDAP 및 JNDI 관련 엔드포인트를 보호하지 않습니다.
Log message, 또는 Log message parameter에 접근하여 제어할 수 있는 공격자는 LDAP 서버에서 로드 된 임의의
코드를 마음대로 실행할 수 있으며, 사용자의 컴퓨터를 원격으로 조종할 수 있습니다.
Logj4 2.17.0 버전부터는 이 취약점을 사용할 수 없도록 업데이트 되었으므로 빠른 업데이트를 강력히 권장합니다.

취약점 영향

취약점이 발생할 수 있는 버전의 Log4j를 통해 신뢰할 수 없는 데이터 또는 사용자가 제어한 데이터 로그를 남길 때 해당 프로그램에 대한 RCE(원격 코드 실행) 취약점이 발생할 수 있습니다.
해당 취약점은 해커가 서버에 로그인한 것 만으로 사용자의 컴퓨터를 원격조종할 수 있기 때문에 심각도가 매우 높으므로 가능한 빠른 조치를 권고합니다.

영향을 받는 버전

Apache Log4j 2.0 Beta9 ~ 2.16.0 사이에 해당하는 모든 버전 및 Apache Log4j 2를 사용하는 제품은 모두 업데이트를 강력히 권장합니다.

제품 목록 확인하기

Log4j 1.x 버전은 이번 취약점에 직접적인 영향을 받는 버전은 아니지만,
제품 업그레이드 지원이 중지되었으며, 또 다른 RCE 공격 벡터에 노출되어 취약한 상태일 수 있으므로 가능하다면 2.16.0 버전으로 마이그레이션 할 것을 권고합니다.

마이그레이션 가이드

취약점 대응방안

이 취약점은 Log4j 2.17.0 버전 업데이트로 해결이 가능합니다.
Log4j 2.15.0 및 2.16.0 버전은 일부 환경 구성(macOS)에서 불완전하거나 JNDI Lookup 패턴 취약,
Lookup evaluation에서 무한 재귀 보호 불가 등으로 DOS 공격이 발생할 수 있는 여지가 존재하므로 반드시 최신 2.17.0 버전을 설치하세요.

업데이트 다운로드

Log4j 2.x 버전

Java 8 및 그 이후 버전을 이용중이라면 Log4j 2.17.0으로 업데이트 해야 합니다.
Java 7 을 이용중이라면 Log4j 2.12.2으로 업데이트 해야 합니다.

업그레이드가 불가능한 경우, 클라이언트 및 서버 측 컴포넌트 모두에서 시스템 프라퍼티 설정이 아래와 같이 되어있는지 확인하세요.

-Dlog4j2.formatMsgNoLookups=true

만약 Log4j 패치가 어려운 경우에는 다음과 같이 진행이 가능합니다.

버전 확인하는 방법

Log4j가 설치된 경로의 pom.xml 파일을 열어 log4j-core로 검색하여 version 확인 가능

버전 2.0-beta9 ~ 2.15.0

JndiLookup 클래스를 경로에서 제거합니다.

zip -q -d log4j-core-*.jar org/apache/logging/log4j/core/lookup/JndiLookup.class

버전 2.16.0

로깅 설정의 PatternLayout에서 Context Lookups를 ${ctx:loginId} 또는 \({ctx:loginId}처럼 스레드 컨텍스트 맵 패턴(%X, %mdc, 또는 %MDC)으로 바꾸세요. 혹은 설정에서 HTTP 헤더 또는 사용자의 입력과 같은 앱 외부 소스에서 발생하는 ${ctx:loginId} 또는\){ctx:loginId}와 같은 Context Lookups에 대한 참조를 제거하세요.

이 취약점은 Log4j-core JAR 파일에만 유효하며, log4j-api JAR 파일만 사용하는 앱은 이번 취약점과 무관합니다.
또한 Log4net, Log4cxx와 같은 Apache의 타 로깅 서비스들 역시 이번 취약점과 무관합니다.

작업한 방법

ext['log4j2.version'] = '2.16.0'

log4j2

로 처리했습니다. 그리고 2.17.0 이 나왔다…고 합니다.

실제 서버에서는 2.15에서도 문제없는 상태라 우선 배제했습니다. 실제 서비스에서는 전부 logback 을 사용하여 문제가 없음.

Dec 17, 2021 - iOS netrwork library 인 alamofire 에서 내는 에러

responseSerializationFailed(reason: 
Alamofire.AFError.ResponseSerializationFailureReason.jsonSerializationFailed(error: 
Error Domain=NSCocoaErrorDomain Code=3840 "Unexpected end of file during string parse 
(expected low-surrogate code point but did not find one). around line 1, column 340." 
UserInfo={NSDebugDescription=Unexpected end of file during string parse 
(expected low-surrogate code point but did not find one). around line 1, column 340., 
NSJSONSerializationErrorIndex=340}))
() $R0 = {}

아이폰에서 이모지

뒷글자 3자리를 자르는 작업시, 이모지가 정상적으로 완성되지 않음. 웹, 안드로이드는 깨진글자가 그대로 동작하나, 아이폰은 json 자체가 파싱이 안되는 현상이 있음.

자바 1.8부터 제공하는


new StringBuilder().appendCodePoint(
  emostring.codePointAt(emostring.offsetByCodePoints(0, 1))).toString()

copePointAt을 이용하고자했는데, 그냥 BreakIterator.getCharacterInstance() 를 이용하여 처리했습니다. (회사의 테크리더 팀장님이 공유해주심. )


public class GraphemeStringProxy {
    private final String originString;
    private final List<String> parsedString;

    public GraphemeStringProxy(String originString) {
        this.originString = originString;
        this.parsedString = splitChars(originString);
    }

    private List<String> splitChars(String str) {
        List<String> parsed = new ArrayList<>();

        BreakIterator it = BreakIterator.getCharacterInstance();
        it.setText(str);
        int index1 = 0;
        int index2 = it.next();
        while (index2 != BreakIterator.DONE) {
            parsed.add(str.substring(index1, index2));
            index1 = index2;
            index2 = it.next();
        }

        return parsed;
    }

    public int length() {
        return parsedString.size();
    }

    public boolean isEmpty() {
        return parsedString.isEmpty();
    }

    public String substring(int startIndex, int endIndex) {
        StringBuffer sb = new StringBuffer();
        parsedString.stream().skip(startIndex).limit(endIndex - startIndex).forEach(sb::append);
        return sb.toString();
    }
}

위와 같이 최종적으로 처리함.

글자 수를 정확하게 카운트 하는 법 [참고주소] : https://engineering.linecorp.com/ko/blog/the-7-ways-of-counting-characters/

Dec 13, 2021 - locust 부하 테스트

부하테스트를 jmatter 를 사용해서 처리하는데,
SRE팀에서 locust 를 적용하여 간략하게 작한 한 내용을 정리함.

참고주소 : (https://locust.io/)

pip3 install locust
locust --version

locust 작성 문법

해당 소스는 외부 에러를 발생했을 때,
서버가 내려가는 현상을 확인하여, 해당 현상 재현을 위한 부타테스트.


from locust import HttpUser, TaskSet, task, between, constant, SequentialTaskSet
from locust.contrib.fasthttp import FastHttpUser

class MetricsTaskSet(SequentialTaskSet):
  _body = None
  _body_delivery_possible = None
  _header_authoriztion = None
  _test_set_number = 0


  def on_start(self):
    print('on_sart is called on task class!', self._test_set_number)

  def on_stop(self):
    print('on_stop is called on task class!', self._test_set_number)

  def setup(self):
    print('setup is called on task class!')

  def teardown(self):
    print('teardown is called on task class!')

  @task
  def case_1(self):

    print(f'[{self._test_set_number}] Case #1 go')

    self.client.get(
      f'/c:%252e%252e%255f%252e%252e%255f%252e%252e%255f%252e%252e%255f%252e%252e%255f%252e%252e%255fboot.ini%23vt/test',
      name=f'/c:%252e%252e%255f%252e%252e%255f%252e%252e%255f%252e%252e%255f%252e%252e%255f%252e%252e%255fboot.ini%23vt/test',
      headers=self._header_authoriztion
    )

  @task
  def case_2(self):

    print(f'[{self._test_set_number}] Case #2 go')

    self.client.get(
      f'/c:.....//.....//.....//.....//.....//.....//etc/passwd%23vt/test',
      name=f'/c:.....//.....//.....//.....//.....//.....//etc/passwd%23vt/test',
      headers=self._header_authoriztion
    )


class MetricsLocust(FastHttpUser):
  tasks = {MetricsTaskSet}
  wait_time = constant(1)

  def setup(self):
    print('MetricsLocust setup is called on locust class !')

  def teardown(self):
    print('MetricsLocust teardown is called on locust class!')

locust 실행

locust --host={테스트할도메인} --locustfile=./{실행파일}.py

부하 테스트 결과는 외부 공격에 의해 fluent 가 OOM 이 발생함.
fluent 는 로그성 컨테이너이므로, 해당 컨테이너가 내려가더라도 java application 이 중지되면 안되는 것을 확인하여, 테스트 종료.

해당 TASK 안에 컨테이너가 2개가 올라가있음 사이드카로 fluent가 동작되고 있음.
Essential container 의 필수값 옵션에 의해 fluent가 죽으면 자동으로 JVM 컨테이너도 강제 재시작됨.