⦿ 국제 TF, 최대 규모 다크마켓 서비스 차단 - 2021.01.14

 

⦿ 세계 최대 규모의 Carding 웹 사이트 Joker's Stash 폐쇄 예정 - 2021.01.16

 

⦿ Apple, 인도 해커 2명에게 버그바운티 상금 $50,000 지급 - 2021.01.18

 

⦿ SolarWinds 해커, Malwarebytes 내부 이메일에 액세스 - 2021.01.20

 

 

 

 

'기타' 카테고리의 다른 글

기술 문서 자료 (2021.01 - 4주)  (0) 2021.01.29
주요 뉴스 (2021.01 - 4주)  (0) 2021.01.28
주요 뉴스 (2021.01 - 3주)  (0) 2021.01.25
기술 문서 자료 (2021.01 - 3주)  (0) 2021.01.22
주요 뉴스 (2021.01 - 2주)  (0) 2021.01.18
기술 문서 자료 (2021.01 - 2주)  (0) 2021.01.14

[Exploitation]

⦿ An incorrect calculation bug in the linux kernel EBPF Verifier - Lucas Leong

https://www.zerodayinitiative.com/blog/2021/1/18/zdi-20-1440-an-incorrect-calculation-bug-in-the-linux-kernel-ebpf-verifier

 

⦿ Chrome 1-Day Hunting - Uncovering and Exploiting CVE-2020-15999

https://starlabs.sg/blog/2021/01/chrome-1-day-hunting-uncovering-and-exploiting-cve-2020-15999

 

⦿ CVE-2020-5144 – SonicWall Global VPN New Elevation of Privileges Vulnerability - Chen Erlich

https://cymptom.com/cve-2020-5144-sonicwall-global-vpn-new-elevation-of-privileges-vulnerability/2021/01/?fbclid=IwAR1UPp-OOfGQRMpsy0WFbcUKAAFriSPUKEX-v2paJJbJuOojvP1924wCH8c

 

⦿ CVE-2020-9971 Abusing XPC Service mechanism to elevate privilege in macOS/iOS

https://xlab.tencent.com/en/2021/01/11/cve-2020-9971-abusing-xpc-service-to-elevate-privilege

 

⦿ Escaping VirtualBox 6.1: Part 1 - Sauercl0ud, a2nkf, localo

https://secret.club/2021/01/14/vbox-escape.html

 

⦿ Exploiting CVE-2014-3153 (Towelroot)

https://elongl.github.io/exploitation/2021/01/08/cve-2014-3153.html

 

⦿ Hunting for Bugs in Windows Mini-Filter Drivers - James Forshaw

https://googleprojectzero.blogspot.com/2021/01/hunting-for-bugs-in-windows-mini-filter.html

 

⦿ ROCESS HERPADERPING – WINDOWS DEFENDER EVASION - Pentest Laboratories Ltd

https://pentestlaboratories.com/2021/01/18/process-herpaderping-windows-defender-evasion/

 

⦿ The State of State Machines - Natalie Silvanovich

https://googleprojectzero.blogspot.com/2021/01/the-state-of-state-machines.html

 

 

 

[Web]

⦿ CVE-2021-2109 Weblogic Remote Code Execution involving HTTP protocol and JNDI injection gadget

https://mp.weixin.qq.com/s/wX9TMXl1KVWwB_k6EZOklw

 

⦿ Exploiting Error Based SQL Injections & Bypassing Restrictions

https://medium.com/bugbountywriteup/exploiting-error-based-sql-injections-bypassing-restrictions-ed099623cd94

 

⦿ Finding 0day to hack Apple - @rootxharsh & @iamnoooob

https://github.com/httpvoid/writeups/blob/main/Apple-RCE.md

 

 

 

[Network]

⦿ DNSpooq - Kaminsky attack is back!

https://www.jsof-tech.com/disclosures/dnspooq/

 

⦿ Cache poisoning in popular open source packages - Adam Goldschmidt

https://snyk.io/blog/cache-poisoning-in-popular-open-source-packages/

 

 

 

[malware]

⦿ A Deep Dive into Lokibot Infection Chain

https://blog.talosintelligence.com/2021/01/a-deep-dive-into-lokibot-infection-chain.html

 

⦿ Building a Custom Malware Analysis Lab Environment - MARCO FIGUEROA

https://labs.sentinelone.com/building-a-custom-malware-analysis-lab-environment/

 

⦿ From A to X analyzing some real cases which used recent Emotet samples

https://blog.vincss.net/2021/01/re019-from-a-to-x-analyzing-some-real-cases-which-used-recent-Emotet-samples.html

 

⦿ Linux.Midrashim: Assembly x64 ELF virus - @guitmz

https://www.guitmz.com/linux-midrashim-elf-virus/

 

⦿ Raindrop: New Malware Discovered in SolarWinds Investigation

https://symantec-enterprise-blogs.security.com/blogs/threat-intelligence/solarwinds-raindrop-malware

 

⦿ Sunburst backdoor – code overlaps with Kazuar - Kaspersky

https://securelist.com/sunburst-backdoor-kazuar/99981

 

 

[기타]

⦿ How to use Ghidra to Reverse Engineer Mobile Application - K O M A L

https://medium.com/bugbountywriteup/how-to-use-ghidra-to-reverse-engineer-mobile-application-c2c89dc5b9aa

 

⦿ Machine Learning for Security

https://security.kiwi/docs/introduction/

 

⦿ macOS Post-Exploitation Shenanigans with VSCode Extensions

https://www.mdsec.co.uk/2021/01/macos-post-exploitation-shenanigans-with-vscode-extensions

 

⦿ Wireshark Tutorial: Examining Emotet Infection Traffic - Brad Duncan

https://unit42.paloaltonetworks.com/wireshark-tutorial-emotet-infection/

 

⦿ Zero Days, Thousands of Nights

https://www.rand.org/pubs/research_reports/RR1751.html

 

 

 

[툴]

⦿ Snort 3 officially released

https://blog.snort.org/2021/01/snort-3-officially-released.html

 

⦿ LIEF 0.11 released

https://lief.quarkslab.com/doc/stable/changelog.html#v0.11.0

 

⦿ Svetovid : Post-exploitation scripts and binaries

https://github.com/redcode-labs/Svetovid

 

⦿ virii : Collection of ancient computer virus source codes : 

https://github.com/guitmz/virii 

 

⦿ lazyweb : Vulnerable Web Application for test

https://github.com/RamadhanAmizudin/lazyweb

 

 

 

 

'기타' 카테고리의 다른 글

기술 문서 자료 (2021.01 - 4주)  (0) 2021.01.29
주요 뉴스 (2021.01 - 4주)  (0) 2021.01.28
주요 뉴스 (2021.01 - 3주)  (0) 2021.01.25
기술 문서 자료 (2021.01 - 3주)  (0) 2021.01.22
주요 뉴스 (2021.01 - 2주)  (0) 2021.01.18
기술 문서 자료 (2021.01 - 2주)  (0) 2021.01.14

⦿ 미군, "Hack The Army" 버그바운티 프로그램 개최 - 2021.01.08

 

⦿ 마이크로소프트, 실제 공격에 악용된 디펜더 백신 프로그램의 제로데이 수정 - 2021.01.13

 

⦿ 구글, 정교한 윈도우 및 안드로이드 해킹 작전 공개 - 2021.01.13

 

 

 

'기타' 카테고리의 다른 글

기술 문서 자료 (2021.01 - 4주)  (0) 2021.01.29
주요 뉴스 (2021.01 - 4주)  (0) 2021.01.28
주요 뉴스 (2021.01 - 3주)  (0) 2021.01.25
기술 문서 자료 (2021.01 - 3주)  (0) 2021.01.22
주요 뉴스 (2021.01 - 2주)  (0) 2021.01.18
기술 문서 자료 (2021.01 - 2주)  (0) 2021.01.14

⦿ Breaking The Browser – A tale of IPC, credentials and backdoors - Dylan(@_batsec_)

 

⦿ CRACKING A CHINESE PROXY TUNNEL: REAL WORLD CTF PERSONAL PROXY WRITEUP - hyperreality

 

⦿ FADE DEAD | Adventures in Reversing Malicious Run-Only AppleScripts - PHIL STOKES

 

⦿ In-the-Wild Series - Google Project Zero

 

⦿ Making Clouds Rain :: Remote Code Execution in Microsoft Office 365 - ϻг_ϻε(@steventseeley)

 

⦿ PHP의 Type Juggling에서 나타나는 취약점(Magic Hashes 취약점)

 

⦿ Real World CTF 발표자료(자막있음)

 

⦿ Stealing Your Private YouTube Videos, One Frame at a Time - David Schütz

 

⦿ Sysmon13 공개  - MS sysinternal

 

⦿ The Mac Malware of 2020 - Patrick Wardle

 

⦿ THE STORY OF CVE-2021-1648 - k0shl

 

⦿ Understanding and Exploiting Zerologon - Siddharth Balyan & Nandini Rana

'기타' 카테고리의 다른 글

기술 문서 자료 (2021.01 - 4주)  (0) 2021.01.29
주요 뉴스 (2021.01 - 4주)  (0) 2021.01.28
주요 뉴스 (2021.01 - 3주)  (0) 2021.01.25
기술 문서 자료 (2021.01 - 3주)  (0) 2021.01.22
주요 뉴스 (2021.01 - 2주)  (0) 2021.01.18
기술 문서 자료 (2021.01 - 2주)  (0) 2021.01.14

VaccineWeb 문제 풀이 보면서 다시 풀어볼 수 있도록 개인 서버에 올렸습니다.

VaccineWeb 문제 사이트 오픈 기간: ~ 2020년 12월 31일까지

서버에 문제 풀이 관련 이외 공격 금지합니다.

 

서버: http://34.228.254.139/vaccineweb/secretdata.php

 

Covid vaccine arrangement page

What you need to do How To Protect Yourself covid virus vaccine data arrangement page

34.228.254.139

 

 

 

 

 

 

           목차

  • 시나리오
  • 정보 수집
  • 코드 분석 및 풀이 stage1
  • 코드 분석 및 풀이 stage2

 

 

 

[Web] VaccineWeb 시나리오
VaccineWeb
시나리오: COVID-19 치료제로 사용될 후보 백신 정보를 정리한 페이지 파일 내용 확인이 불가능한 상태다. 
홈페이지에서 단서를 찾고 백신 정보를 찾아내자!

Scenario: It is impossible to check the contents of the page file containing information on candidate vaccines to be used as a treatment for COVID-19. 
Find clues on the website and identify the vaccine information!

[그림 1] - VaccineWeb Main Page (secretdata.php)

시나리오는 COVID-19 치료제로 사용될 후보 백신 정보를 정리한 페이지 파일 내용 확인이 불가능하다고 한다. 홈페이지에서 단서를 찾고 백신 정보를 찾아내자고 적혀 있는데 파일 내용을 확인할 수 있어야 한다는 것을 알 수 있다.

이 점을 기억하고 추가적인 정보를 수집하자.

 

 

 

정보 수집

[그림 2] - data input

[그림 1]에 있던 DATA INPUT 입력 폼에 임의 값 1234를 넣고 DATA INPUT 버튼을 누르면 [그림 2]와 같이 dataprocessing.php 페이지로 이동되고 data input success라는 문자열이 출력된다. 다른 값을 넣더라도 [그림 2]와 동일한 장면이 나오는 것을 확인할 수 있다.

 

 

 

[그림 3] - fake flag

[그림 1] 메인 페이지에서 밑으로 조금 내려가면 [그림 3]이 보이는데 flag is c0v1d_c1e4r 라고 되어 있고 오른쪽에는 you find! :)라고 되어있다. 해당 내용은 진짜 flag가 아니다.

 

 

 

[그림 4] - Prevention (prevention.php)

prevention 탭을 누르면 prevention.php로 이동된다. 해당 탭 안에서는 시나리오에서 제시한 파일 내용 확인 관련 단서는 없는 것을 확인할 수 있다.

 

 

 

 

[그림 5] - Login (secretaccount.php)

Login 탭을 누르자 로그인 페이지가 나왔고 로그인 관련 사항으로 "Are you confident in logging in?" 로그인에 자신있냐는 질문과 로그인에 실패시 바이러스라는 문자열이 포함되어 있다.

 

그 아래에 ID와 Password 입력 폼과 LOGIN ACCESS, LOGIN PROCESSING SOURCE CODE 버튼이 있다.

 

 

 

 

[그림 6] - 로그인 임의 값 입력

1234, 1234로 로그인을 시도하자.

 

 

 

 

 

[그림 7] - alert 'virus' (login.php)

LOGIN ACCESS 버튼을 클릭하면 login.php 페이지로 이동하고 alert 메시지 박스에 올바르지 않은 값이므로 바이러스 페이지로 이동한다는 내용이 출력된다.

 

 

 

 

[그림 8] - virus.php

virus.php 페이지로 이동되고 다른 영향은 없고 페이지만 바뀐 것을 확인할 수 있다. 

 

 

 

 

 

[그림 9] - LOGIN PROCESSING SOURCE CODE

다시 Login 페이지(secretaccount.php)로 돌아와서 로그인 과정을 확인하기 위해 LOGIN PROCESSING SOURCE CODE 버튼을 클릭하면 [그림 9]와 같이 백신 이미지가 있던 부분이 소스 코드 이미지로 변경되어 출력된다.

 

여기까지 정보 수집 결과로는 메인 페이지(secretdata.php)에 있던 data input 폼과 회원가입이 없이 로그인만 해야 하는 로그인 페이지(secretaccount.php) 이 두 부분이 해당 문제의 핵심이자 단서라고 볼 수 있다. 로그인을 해야 하는 것과 data input 폼에는 올바른 값을 넣어야 하는 것으로 보면 된다.

 

 

코드 분석 및 풀이 stage1
<?php
    $fake = $_GET['id'];
    $id = "virus";
    extract($_GET);
    $pw = $_GET['pass'];

    if(preg_match('/medic1ne/', $id))
    {
        echo("<script> location.href = 'virus.php'; </script>");
    }

    if(stristr($id, "medic1ne") && strcmp($pwcheck, $pw) == 0)
    {
        echo("<script> alert('success \$flag = ?'); </script>");
    }
    else
    {
    	echo("<script> alert('incorrect value so virus page!!!!!'); </script>");
        echo("<script> location.href = 'virus.php'; </script>");
    }
?>

Login 페이지에서 제공한 소스 코드를 분석하자

1. $fake 변수에 id 값을 받아온다.

2. $id 변수에는 virus 문자열이 지정되어 있다.

3. $fake, $id 이후 extract()함수가 진행된다.

4. $pw 변수에 pass 값을 받아온다.

5. if 문에서 preg_match를 통해 medic1ne이 $id 변수에 있을 경우 virus 페이지로 이동시킨다.

6. 두 번째 if문에서 medic1ne이 있는지 검사하는데 대소문자를 구분하지 않는다. 그리고 $pw와 $pwcheck 변수 값이 같은지 검사하고 있다. 여기 값이 true가 되면 flag 값을 확인할 수 있고 false인 경우 else문이 실행되어 [그림 7]에서 본 메시지 박스 출력과 함께 virus page로 이동하게 된다.

 

여기서 취약한 부분을 아래의 표와 같이 정리했다.

함수 이유
extract() 변수 선언 전에 사용해야 하는데 선언 후에 했기 때문에 전에 변수 값을 클라이언트 측에서 마음대로 변경이 가능하다.
즉, GET 방식이므로 login.php?id=medic1ne으로 새롭게 지정이 가능하다.
preg_match() preg_match에서 /medic1ne/는 대소문자 구분 없이 패턴을 찾는게 아니라 구분을 한다. 즉, /medic1ne/i로 해야 MEDIC1NE, Medic1ne 이렇게 대소문자가 섞여 있는 것도 찾을 수 있다. 하지만 위와 같이 구분을 하기 때문에 meDic1ne 이렇게 대문자 하나를 포함시켜주면 우회할 수 있다.
stristr() 위 분석 6번에서 언급한 것과 같이 대소문자를 구분하지 않는 strstr() 함수로 볼 수 있다. medic1ne을 $id 값과 비교하는데 대소문자를 구분하지 않는다. 즉, meDic1ne을 입력하면 우회할 수 있다.

preg_match와 stristr 둘 다 우회해야 하기 때문에 medic1ne을 그대로 입력하면 preg_match에 걸리는 대신 stristr은 우회된다. 하지만 virus.page로 이동된다. 그래서 둘 다 우회할 수 있는 meDic1ne 또는 MeDic1ne 등 대소문자를 섞거나 대문자로 넣어야 한다.
strcmp() strcmp 함수의 경우 비교할 시 ===로 엄격하게 하는 것이 아닌 ==로 하게 되면 배열로 입력을 변경시키면 True가 된다. 

정리하면 login.php?id=meDic1ne&pass[]= 이렇게 입력하면 stage1 코드 전부 우회하여 success로 접근할 수 있다.

 

 

 

 

 

[그림 10] - login success

위 입력 값 그대로 적용하면 success 문구와 함께 첫 번째 flag 값을 알려준다.

 

 

 

[그림 11] - second_flag (base64)

이후 확인을 누르면 [그림 11]과 같이 두 번째 flag 값이라고 출력되는데 base64로 보이는 코드가 나온다.

 

decode 사이트 또는 python 등을 이용하여 base64 디코딩을 진행하면

"Find and see vaccine.php" 라는 문자열로 변환된다.

vaccine.php 를 찾고 시나리오에 맞춰서 해당 파일 내용을 확인해야 한다는 의미다.

당연히 vaccine.php로 그냥 접근하면 아무것도 확인할 수 없다.

 

 

 

[그림 12] - stage2 (dataprocessing.php source code)

두 번째 플래그 메시지 박스 확인을 누르면 [그림 12]와 같이 새로운 소스 코드를 제공하는데 출력하는 echo 문자열들을 보면 [그림 2]에서 DATA INPUT 폼에 입력 후 이동되어 출력된 문자열과 같다는 것을 알 수 있다. "data input success"가 해당 문자열이다.  [그림 12] 소스 코드를 분석하여 vaccine.php를 확인해야 한다.

 

 

 

코드 분석 및 풀이 stage2
<?php
    class medicine {
        public function __wakeup() {
            return highlight_file($this->input);
        }
    }

    class printstr {
        public function __wakeup() {
            return 'your input value: ' . $this->input;
        }
    }

    if(isset($_POST['input'])) {
        $data = ~~~~~~~~~~~~ $_POST['input']);

        unserialize($data);
        echo "<br><br> data input success";
    }

    else {
        echo("<script> location.href = 'virus.php'; </script>");
    }

?>

medicine이라는 클래스를 주목하자.

highlight_file($this->input)이 되어있는데 이 부분은 php코드를 그대로 노출 시켜주는 코드이다.

즉, 우리가 찾는 vaccine.php 내용을 노출 시켜서 확인할 수 있다는 것이다.

그리고 [그림 12]를 보면 '메인 페이지 이동' 이라고 친절하게 표시가 되어있다. 이 말은 메인 페이지에서 봤던 [그림 1]에서 접근한 DATA INPUT 폼 양식을 이용하라는 것이다. 해당 폼에 placeholder 값으로도 input vaccine data라고 되어있다.

 

highlight_file을 다루는 메서드와 your input value를 출력하는 메서드는 동일한 메서드로 __wakeup 메서드다.

이 메서드는 PHP 매직 함수, PHP 매직 메서드라고 불리는데 직렬화 또는 역직렬화에 반응하여 실행되는 특징을 가지고 있다. 바로 밑에 unserialize($data); 를 보면 역직렬화를 진행하고 있는 것을 볼 수 있다.

 

$data는 우리가 입력하려는 input 값을 POST 방식으로 받아서 $data 변수에 담고 해당 변수를 역직렬화를 수행하는 것이다. [그림 12]를 보면 이상한 점이 POST 앞 부분을 검정색 네모 박스로 조금 가려져 있는 것을 볼 수 있다. 무슨 이유일까? 하지만 오른쪽에 코드를 보면 ' ) ' 로 포함되어 있는 것으로 볼 때 다른 함수에 포함된 것으로 추측할 수 있다.

 

결론을 말하자면 검정색 가린 부분은 의미가 없다. why? 위 stage2는 PHP Object Injection 취약점이기 때문이다.

 

역직렬화를 입력 받는 부분에 Object Injection을 통해 원하는 값을 출력시킬 수 있다.

└▶ unserialize() 함수가 실행되면 __wakeup() 메서드 자동 실행

 

그렇기 때문에 제공한 소스 코드 dataprocessing.php 파일 내용도 직접 확인할 수 있고 vaccine.php, secretdata.php 등 어느 php 파일이던 다 확인할 수 있기 때문에 직접 가린 부분도 확인할 수 있다.

 

정리하면 다음과 같다.

1. PHP Object Injection 시도를 하다 보면 검정색 가린 부분이 어떤 함수인지 추측할 수 있다.

2. 확실하게 하고 싶으면 dataprocessing.php 를 Object Injection을 통해 확인하여 어떤 함수가 포함되어 있는 건지 확인 후 vaccine.php 파일을 확인하면 된다.

 

자 그럼 PHP Object Injection을 수행하여 해결하자.

~~~~~~> 여기서 잠깐! PHP Object Injection이란 무엇인가? 혹시 모른다면 아래 출제자 블로그에서 개념을 파악한 뒤 다시 여기 Demon 팀 블로그로 와서 풀이를 확인하자!

blog.naver.com/lstarrlodyl/222172446060

 

PHP Object Injection

장치나 인명의 안전을 위한 보안기(arrester)→ arrester 피뢰기에 감동? 받으며 IT보안 공부하는 김주원...

blog.naver.com

 

 

 

 

[그림 13] - vaccine.php find?

O:8:"medicine":1:{s:5:"input";s:11:"vaccine.php";}

 

vaccine.php를 찾기 위해 Object Injection 양식 기준에 맞춰서 입력한 결과 이전과 같은 data input success 문자열만 출력되고 있다. 위 O:8:~ 는 입력 폼이다. 제공한 소스 코드를 참고하여 작성한 Object Injection data 내용이다.

 

 

 

 

 

[그림 14] - secretdata.php

O:8:"medicine":1:{s:5:"input";s:14:"secretdata.php";}

vaccine.php 는 확인할 수 없었기 때문에 secretdata.php를 시도하면 [그림 14]와 같이 정상적으로 코드가 출력되는 것을 볼 수 있다.

 

 

 

 

 

 

[그림 15] - login.php

O:8:"medicine":1:{s:5:"input";s:9:"login.php";}

login.php (stage1) first_flag를 찾았던 로그인 페이지를 확인하면 마찬가지로 [그림 15]를 보면 [그림 14]와 동일하게 코드가 정상적으로 출력된다. 여기까지 확인하면 위에서 말한 검정색 가린 부분이 어떤 함수인지 추측할 수 있다는 것이다.

 

물론 추측이기에 정확하게 알아내려면 dataprocessing.php를 Object Injection 입력 폼에 맞춰서 입력하여 확인하면 된다.

 

추측이 가능한 부분은 vaccine.php를 검색하였을 때 정상적으로 되지 않았기 때문에 vaccine 해당 문자열을 감지하여 무언가 다른 역할을 하도록 만들었다고 생각할 수 있다. 하나 떠오르는 것은 치환이다. 다른 문자열로 입력되도록 만들면 data input success만 출력되도록 할 수 있다.

 

정확한 확인을 위해 dataprocessing.php를 확인하자.

 

 

 

 

 

[그림 16] - dataprocessing.php

O:8:"medicine":1:{s:5:"input";s:18:"dataprocessing.php";}

dataprocessing.php를 확인하면 예상대로 치환을 해주고 있고, 이용한 함수는 str_replace이다. vaccine을 확인하면 ""으로 변환시켜서 입력된다.

 

그러면 어떻게 입력하면 될까? str_replace 취약점을 이용하면 된다.

 

vaccine이 사라지고도 vaccine이 맞춰지면 되기 때문에 vacvaccinecine 이렇게 입력하고 object injection 폼 기준은 그대로 사라지고 남을 string length로 맞춰준다. 즉, vaccine.php의 길이는 11이다. vacvaccinecine는 14인데 replace되기 때문에 그대로 11로 넣어야 한다.

 

 

 

 

[그림 17] - vaccine.php

O:8:"medicine":1:{s:5:"input";s:11:"vacvaccinecine.php";}

위 폼과 동일하게 넣으면 [그림 17]과 같이 second_flag를 확인할 수 있고, 전체 flag는 first_flag와 second_flag를 합친 값이라고 나와있다.

 

$flag = POX{v4cc1ne_d4t4_f1nd_chingchanhae}

'CTF' 카테고리의 다른 글

POX 2020 CTF - VaccineWeb Write Up  (0) 2020.12.14
POX 2020 CTF - Kiban64  (0) 2020.11.25
POX 2020 CTF - Mobile Pentest Write Up  (0) 2020.11.23
Power of XX 2020 Write up  (0) 2020.11.21
DownUnderCTF - Is this pwn or web? write-up  (0) 2020.09.28

writeup 링크

https://g0pher.kr/Power-Of-XX-2020-Kiban64-writeup-27110c7354e147faaa23f7afd70cd269

 

POC

let strings = "ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz";
strings += "0123456789_-$^{}";
let table = {};

function get_table() {
    /*
     * 조작된 인코딩 테이블을 구하는 함수.
     */
    var text = '';
    for (var i=0; i < 1000; i ++) {
        text += strings[Math.floor(Math.random() * strings.length)];
    }
    var b64text = btoa(text).replace(/\=/g, '');
    return new Promise(function(resolve) {
        fetch('/encode/'+text).then(r => r.json()).then(function(resp){
            if (resp.result.length == b64text.length) {
                resp.result.split('').forEach(function(c, i) {
                    table[c] = b64text[i];
                });
            }
            resolve(text);
        });
    });
}

function kiban2base() {
    /*
     * 조작된 인코딩 테이블을 기반으로
     * 패턴 데이터를 base64 인코딩 데이터로 변환하는 함수.
     */
    return new Promise(function(resolve) {
        fetch('/pattern').then(r=>r.json()).then(function(resp) {
            timeline = Object.keys(resp).sort();
            data = timeline.map(t => table[resp[t]]).join('');
            resolve(data);
        });
    });
}

get_table().then(kiban2base).then(atob).then(console.log);

'CTF' 카테고리의 다른 글

POX 2020 CTF - VaccineWeb Write Up  (0) 2020.12.14
POX 2020 CTF - Kiban64  (0) 2020.11.25
POX 2020 CTF - Mobile Pentest Write Up  (0) 2020.11.23
Power of XX 2020 Write up  (0) 2020.11.21
DownUnderCTF - Is this pwn or web? write-up  (0) 2020.09.28

                목차

  • 시나리오
  • 정보 수집
  • 코드 분석
  • frida 풀이

 

[MISC] Mobile Pentest 시나리오
Mobile Pentest
시나리오: COVID 치료제 정보에 접근할 수 있는 암호 값이 내장된 애플리케이션을 받았다. 
하지만 바이러스가 애플리케이션까지 감염되어 암호 값을 확인할 수 없게 되었다. 암호 값을 확인하자!

Scenario: An application including password values to access information for COVID treantment was received.
However, the password values cannot be verified due to the virus that has infected the application.
Let's find the password values!

[그림 1] mobilepentest.apk

시나리오와 함께 문제 파일이 주어지는데 받아서 녹스(Nox)로 실행하면 [그림 1]과 같이 mobilepentest.apk 파일이 설치된다.

 

시나리오 내용에서 먼저 얻을 수 있는 정보로는 암호 값이 내장된 앱이며 해당 암호 값을 확인해야 한다는 것이다.

그러면 직접 apk 실행을 통해 추가적인 정보를 수집하자

 

 

정보 수집

[그림 2] 실행 화면

앱을 실행하면 "COVID 치료제 정보 확인"이라는 버튼 하나가 있는 것을 볼 수 있다.

 

 

[그림 3] medicine?

버튼을 누르면 메시지 박스창이 나오는데 제목은 코로나 치료제를 알고 있는지와 해당 치료제는 어디 있는지 묻는 내용이 포함되어 있다.

 

 

[그림 4] 체크 버튼 후

CHECK 버튼을 누르면 "check ok :)"라는 Toast.makeText 메시지가 출력된다.

 

"COVID 치료제 정보 확인" 버튼을 눌렀을 때 다른 반응이 나오게 하거나 앱 자체 내에 flag 값이 숨어 있을 것으로 보인다. 이유는 시나리오에서 언급한 것과 앱 실행 화면에서는 다른 단서를 찾을 수 없기 때문에 apk 파일을 직접 뜯어야 한다.

 

코드 분석

 

jadx-gui 도구를 이용하여 apk 내부 소스코드를 확인할 수 있다. 또는 웹에서 제공하는 디컴파일을 이용하는 방법도 있다.

 

package com.example.mobilepentest;

import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import java.io.InputStream;

// 1번 영역====================================================
public class MainActivity extends AppCompatActivity {
    static int input1 = 2;
    static int input2 = 2;
    static int input3 = 2;
    static int input4 = 2;
    String cryptotext = "";
    String flag = "where is medicine?";
    String flag0 = "";
    String flag1 = "";
    String flag2 = "";
    String flag3 = "";
    String flag4 = "";
    String flag5 = "";
    String title1 = "Do you know COVID medicine?";
    
// 1번 영역====================================================

// 2번 영역====================================================

    public void medicine() {
        try {
            InputStream openRawResource = getResources().openRawResource(R.raw.flag);
            byte[] bArr = new byte[openRawResource.available()];
            openRawResource.read(bArr);
            this.flag0 = new String(bArr);
        } catch (Exception unused) {
            this.flag = "fail";
        }
        this.title1 = "COVID medicine data";
        String str = this.flag0;
        this.cryptotext = str;
        this.flag1 = str.substring(15, 16);
        this.flag1 += this.cryptotext.substring(14, 15);
        this.flag1 += this.cryptotext.substring(23, 24);
        this.flag1 += this.cryptotext.substring(69, 70);
        if (input1 == 2) {
            this.flag2 = this.cryptotext.substring(38, 39);
            this.flag2 += this.cryptotext.substring(71, 72);
            this.flag2 += this.cryptotext.substring(27, 28);
            this.flag2 += this.cryptotext.substring(34, 35);
            this.flag2 += this.cryptotext.substring(72, 73);
            this.flag2 += this.cryptotext.substring(30, 31);
            this.flag2 += this.cryptotext.substring(52, 53);
        } else {
            this.flag2 = this.cryptotext.substring(5, 6);
            this.flag2 += this.cryptotext.substring(0, 1);
            this.flag2 += this.cryptotext.substring(10, 11);
            this.flag2 += this.cryptotext.substring(4, 5);
            this.flag2 += this.cryptotext.substring(52, 53);
        }
        if (input2 == 0) {
            this.flag3 = this.cryptotext.substring(75, 76);
            this.flag3 += this.cryptotext.substring(39, 40);
            this.flag3 += this.cryptotext.substring(29, 30);
            this.flag3 += this.cryptotext.substring(52, 53);
        } else {
            this.flag3 = this.cryptotext.substring(5, 6);
            this.flag3 += this.cryptotext.substring(0, 1);
            this.flag3 += this.cryptotext.substring(10, 11);
            this.flag3 += this.cryptotext.substring(4, 5);
            this.flag3 += this.cryptotext.substring(52, 53);
        }
        if (input3 == 2) {
            this.flag4 = this.cryptotext.substring(31, 32);
            this.flag4 += this.cryptotext.substring(43, 44);
            this.flag4 += this.cryptotext.substring(72, 73);
            this.flag4 += this.cryptotext.substring(29, 30);
            this.flag4 += this.cryptotext.substring(75, 76);
            this.flag4 += this.cryptotext.substring(52, 53);
        } else {
            this.flag4 = this.cryptotext.substring(5, 6);
            this.flag4 += this.cryptotext.substring(0, 1);
            this.flag4 += this.cryptotext.substring(10, 11);
            this.flag4 += this.cryptotext.substring(4, 5);
            this.flag4 += this.cryptotext.substring(52, 53);
        }
        if (input4 == 0) {
            this.flag5 = this.cryptotext.substring(32, 33);
            this.flag5 += this.cryptotext.substring(71, 72);
            this.flag5 += this.cryptotext.substring(76, 77);
            this.flag5 += this.cryptotext.substring(46, 47);
            this.flag5 += this.cryptotext.substring(70, 71);
        } else {
            this.flag5 = this.cryptotext.substring(5, 6);
            this.flag5 += this.cryptotext.substring(0, 1);
            this.flag5 += this.cryptotext.substring(10, 11);
            this.flag5 += this.cryptotext.substring(4, 5);
            this.flag5 += this.cryptotext.substring(70, 71);
        }
        this.flag = this.flag1 + this.flag2 + this.flag3 + this.flag4 + this.flag5;
    }
    
// 2번 영역====================================================

// 3번 영역====================================================

    public void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        setContentView((int) R.layout.activity_main);
        ((Button) findViewById(R.id.button1)).setOnClickListener(new View.OnClickListener() {
            public void onClick(View view) {
                AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
                builder.setTitle(MainActivity.this.title1);
                builder.setMessage(MainActivity.this.flag);
                builder.setPositiveButton("check", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialogInterface, int i) {
                        Toast.makeText(MainActivity.this, "check ok :)", 0).show();
                    }
                });
                builder.show();
            }
        });
    }
}

// 3번 영역====================================================

이해를 돕기 위해 코드에 "1번, 2번, 3번 영역" 주석으로 코드 범위를 지정하였다.

 

 

[1번 영역]

// 1번 영역====================================================
public class MainActivity extends AppCompatActivity {
    static int input1 = 2;
    static int input2 = 2;
    static int input3 = 2;
    static int input4 = 2;
    String cryptotext = "";
    String flag = "where is medicine?";
    String flag0 = "";
    String flag1 = "";
    String flag2 = "";
    String flag3 = "";
    String flag4 = "";
    String flag5 = "";
    String title1 = "Do you know COVID medicine?";
    
// 1번 영역====================================================

전역 변수로 input1,2,3,4 와 cryptotext 그리고 우리가 찾으려는 flag로 보이는 flag 변수와 아까 앱에서 실행해서 확인한 문자열을 담고 있는 title1 변수가 있다.

 

 

[2번 영역]

// 2번 영역====================================================

    public void medicine() {
        try {
            InputStream openRawResource = getResources().openRawResource(R.raw.flag);
            byte[] bArr = new byte[openRawResource.available()];
            openRawResource.read(bArr);
            this.flag0 = new String(bArr);
        } catch (Exception unused) {
            this.flag = "fail";
        }
        this.title1 = "COVID medicine data";
        String str = this.flag0;
        this.cryptotext = str;
        this.flag1 = str.substring(15, 16);
        this.flag1 += this.cryptotext.substring(14, 15);
        this.flag1 += this.cryptotext.substring(23, 24);
        this.flag1 += this.cryptotext.substring(69, 70);
        if (input1 == 2) {
            this.flag2 = this.cryptotext.substring(38, 39);
            this.flag2 += this.cryptotext.substring(71, 72);
            this.flag2 += this.cryptotext.substring(27, 28);
            this.flag2 += this.cryptotext.substring(34, 35);
            this.flag2 += this.cryptotext.substring(72, 73);
            this.flag2 += this.cryptotext.substring(30, 31);
            this.flag2 += this.cryptotext.substring(52, 53);
        } else {
            this.flag2 = this.cryptotext.substring(5, 6);
            this.flag2 += this.cryptotext.substring(0, 1);
            this.flag2 += this.cryptotext.substring(10, 11);
            this.flag2 += this.cryptotext.substring(4, 5);
            this.flag2 += this.cryptotext.substring(52, 53);
        }
        if (input2 == 0) {
            this.flag3 = this.cryptotext.substring(75, 76);
            this.flag3 += this.cryptotext.substring(39, 40);
            this.flag3 += this.cryptotext.substring(29, 30);
            this.flag3 += this.cryptotext.substring(52, 53);
        } else {
            this.flag3 = this.cryptotext.substring(5, 6);
            this.flag3 += this.cryptotext.substring(0, 1);
            this.flag3 += this.cryptotext.substring(10, 11);
            this.flag3 += this.cryptotext.substring(4, 5);
            this.flag3 += this.cryptotext.substring(52, 53);
        }
        if (input3 == 2) {
            this.flag4 = this.cryptotext.substring(31, 32);
            this.flag4 += this.cryptotext.substring(43, 44);
            this.flag4 += this.cryptotext.substring(72, 73);
            this.flag4 += this.cryptotext.substring(29, 30);
            this.flag4 += this.cryptotext.substring(75, 76);
            this.flag4 += this.cryptotext.substring(52, 53);
        } else {
            this.flag4 = this.cryptotext.substring(5, 6);
            this.flag4 += this.cryptotext.substring(0, 1);
            this.flag4 += this.cryptotext.substring(10, 11);
            this.flag4 += this.cryptotext.substring(4, 5);
            this.flag4 += this.cryptotext.substring(52, 53);
        }
        if (input4 == 0) {
            this.flag5 = this.cryptotext.substring(32, 33);
            this.flag5 += this.cryptotext.substring(71, 72);
            this.flag5 += this.cryptotext.substring(76, 77);
            this.flag5 += this.cryptotext.substring(46, 47);
            this.flag5 += this.cryptotext.substring(70, 71);
        } else {
            this.flag5 = this.cryptotext.substring(5, 6);
            this.flag5 += this.cryptotext.substring(0, 1);
            this.flag5 += this.cryptotext.substring(10, 11);
            this.flag5 += this.cryptotext.substring(4, 5);
            this.flag5 += this.cryptotext.substring(70, 71);
        }
        this.flag = this.flag1 + this.flag2 + this.flag3 + this.flag4 + this.flag5;
    }
    
// 2번 영역====================================================

medicine이라는 메서드가 정의되어 있는데 내용을 요약하자면 raw에 존재하는 flag를 가져와서 flag0 변수에 담은 뒤 다시 str 변수를 거쳐서 cryptotext 변수에 대입된다.

이후 flag 변수와 cryptotext 변수가 서로 치환 조건을 거쳐서 각 flag 변수에 들어가게 되고 최종적으로 flag는 flag1,2,3,4,5 변수 값을 합친 값이 된다.

 

여기서 중요한 점은 input1,2,3,4 변수에 각 비교하는 값이 맞는 경우와 틀린 경우 치환하는 값이 다르다는 것이다.

 

 

[3번 영역]

// 3번 영역====================================================

    public void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        setContentView((int) R.layout.activity_main);
        ((Button) findViewById(R.id.button1)).setOnClickListener(new View.OnClickListener() {
            public void onClick(View view) {
                AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
                builder.setTitle(MainActivity.this.title1);
                builder.setMessage(MainActivity.this.flag);
                builder.setPositiveButton("check", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialogInterface, int i) {
                        Toast.makeText(MainActivity.this, "check ok :)", 0).show();
                    }
                });
                builder.show();
            }
        });
    }
}

// 3번 영역====================================================

onCreate 메서드, 앱이 실행될 때 먼저 실행되는 부분이다. Android에서 main 영역으로 볼 수 있다.

내용을 보면 버튼을 눌렀을 때 Toast.makeText로 "check ok :)"가 출력 되도록 작성되어 있다.

해당 문자열은 [그림 4]에서 본 check ok :) 문자열인 것을 확인할 수 있다.

 

여기까지 봤을 때 풀이 방법은 3가지 정도로 볼 수 있다.

1. frida 풀이

2. flag 요청 값을 확인 후 치환 코드 적용

3. flag 요청 값을 확인 후 수동으로 확인

 

우선 문제 제작 의도는 frida를 사용할 줄 아는지 확인하기 위해서다. 그래서 해당 풀이에서는 1번 방법으로 진행한다.

2번과 3번 방법이 거의 비슷하기 때문에 간단하게 2, 3번에 대해 요약해서 설명한다.

 

flag 요청 값을 확인한다는 것이 무슨 말일까? flag는 [2번 영역] 코드 초반에 R.raw.flag로 flag를 불러오고 있다.

앱을 개발 해본 경험이 있으면 2, 3번 방법으로도 풀이가 가능하다. raw라는 폴더에 flag 관련 파일이 포함되어 있다는 것이므로 디컴파일에서 리소스를 거쳐서 raw 폴더를 찾아 flag 관련 파일에 무슨 내용인지 확인하면 된다.

그 내용을 불러와서 치환하고 있기 때문에 안에 내용이 무엇인지 알게 된다면 직접 치환 코드에 적용하거나 수동으로 확인하는 것도 가능하다.

 

그럼 어떻게 frida 풀이라는 것을 확인할 수 있을까?

시나리오 내용을 다시 보자. COVID 치료제 정보에 접근할 수 있는 암호 값이 내장된 애플리케이션이 감염되어 암호 값을 확인할 수 없다고 했다. [3번 영역] 코드를 보면 medicine 메서드를 따로 요청하여 이용하는 것을 볼 수 없다. 보통 메서드나 함수를 정의하면 해당 메서드나 함수를 사용한다는 부분이 메인에 포함되어야 한다. 즉, medicine 메서드는 정의만 되어 있을 뿐 사용되지 않는다.

 

버튼을 클릭할 때 where is medicine이라는 문자열이 계속 출력되었는데 [1번 영역]코드에서 flag(전역 변수)로 지정을 했기 때문이다. medicine 메서드를 실행하면 해당 flag 변수 값이 변동되어 출력된다.

 

그러므로 frida를 통해 medicine 메서드가 실행될 수 있도록 스크립트 코드를 작성하여 로드하면 된다.

[2번 영역] 코드를 보면 input1,2,3,4 변수에 각 비교하는 값이 맞는 경우로 맞춰야 하기 때문에 해당 변수 값 중 틀리게 설정되어 있는 값을 변경해야 한다.

 

전역 변수에서 input1,2,3,4 전부 2를 대입하고 있다.

비교하고 있는 값을 보면 input1 == 2 // input2 == 0 // input3 == 2 // input4 == 0 이다.

그러면 input2와 input4는 0을 대입해줘야 한다.

 

 

 

frida 풀이

frida 세팅을 하고 난 후 아래와 같이 로드할 JavaScript 코드를 작성한다.

 

//exploit.js
setImmediate(function(){
	Java.perform(function(){
		var input = Java.use("com.example.mobilepentest.MainActivity");
		input.input2.value = 0;
		input.input4.value = 0;
		Java.choose("com.example.mobilepentest.MainActivity", {
			onMatch : function(mobilepentest){
				mobilepentest.medicine();
			},
			onComplete : function(){
				console.log("\nflag find it!");
			}
		})
	})
})

코드 내용은 위에 코드 분석에서 언급한 내용들로 작성했다.

 

 

[그림 5] frida exploit.js load

frida를 이용하여 작성한 스크립트를 로드하고 조금 기다리면 flag find it! 문자열이 출력된다. 그리고 "COVID 치료제 정보 확인" 버튼을 클릭하면 [그림 5]와 같이 Do you know COVID medicine? 제목이 medicine 메서드 실행으로 인하여 COVID medicine data로 변경되었고, where is medicine? 문자열 또한 flag 값으로 변경된 상태로 출력된다.

'CTF' 카테고리의 다른 글

POX 2020 CTF - VaccineWeb Write Up  (0) 2020.12.14
POX 2020 CTF - Kiban64  (0) 2020.11.25
POX 2020 CTF - Mobile Pentest Write Up  (0) 2020.11.23
Power of XX 2020 Write up  (0) 2020.11.21
DownUnderCTF - Is this pwn or web? write-up  (0) 2020.09.28

POX 2020 Write Up

문제 리스트


[Web] POX Intranet

문제 페이지에는 연구소 인트라넷 페이지가 구성되어 있다.
이 연구소 인트라넷 페이지에 제공되는 사내 클라우드에는 백신 제작에 중요한 단서가 되는 파일이 있는데,
해당 파일을 열람하기 위해서는 일반 직원의 권한이 아닌,
연구 직원의 권한이 있어야만 파일을 열람할 수 있도록 보안이 되어있다.
문제에서 요구하는 권한을 얻어서 파일을 열람할 수 있습니까?

The Challenge is laboratory Intranet Website
The lab is providing cloud services, Cloud services have important clues to produce the COVID-19 vaccine
You need the authority of the lab staff to access the file.
Can you obtain the authority required in the matter?

문제 페이지에 접속하면 다음과 같은 화면이 표시됩니다.

회원가입 후 로그인을 거쳐 메인 페이지로 접속하신 뒤 게시판(/board)로 접근하시면 다음과 같습니다.

해당 페이지 내 게시글을 읽게 되면 URL 요청으로 /read?idx=게시글ID 형식으로 요청을 하고,

아래와 같은 글 읽기 섹션이 표시됩니다.

여기서 발생하는 취약점은 Union Based SQL Injection 으로, /read?idx=게시글ID 형식으로 요청을 할 때

게시글ID 값에 Injection Query를 발생시키면 다른 데이터베이스, 테이블, 컬럼 및 행 조회가 가능합니다.

?idx=1%27%a0union%a0select%a01,2,3,4,table_name%a0from%a0information_schema.tables--%a0 

위 쿼리문을 입력 했을 시 데이터베이스 내 모든 테이블 정보의 결과를 반환합니다.

1'%a0union%a0select%a01,2,3,4,column_name%a0from%a0information_schema.columns--%a0

위 쿼리문을 입력 하였을 경우에는 테이블 내 컬럼 정보의 결과를 반환합니다.

해당 결과값에서 문제 풀이에 필요한 정보인

1%27%a0union%a0select%a01,2,3,concat(username,%a0auth_token),5%a0from%a0users--%a0

마지막으로, 위와 같이 쿼리를 조합 해주면 user테이블의 username과 auth_token 값을 알 수 있습니다.

765f615f635f635f695f6e5f65 이 값을 hex 디코딩 과정을 거치게 되면 v_a_c_c_i_n_e 이라는 값을

얻을 수 있습니다. 이제 해당 값을 통해 mypage 내 credential을 통해 계정에 관리자 인증을 합니다.

그럼 이전과 다르게, 내 정보의 credential 값이 admin 으로 변경됩니다. 이제 페이지 내 클라우드(/cloud)로

접속합니다.

클라우드 섹션에 접근하면 위 사진과 같이 aws bucket를 제공하는데, 이 bucket의 내부 저장소에 접근하여

특정 문서 파일을 다운로드 할 수 있습니다.

이제 다운로드 된 파일을 열어서 플래그를 찾아 획득합니다.

os 포맷을 해서 docx가 없는 관계상 ㅎㅎ.. 이렇게도 확인 가능합니다.

[MISC] Infected File

최근 코로나19에 따른 보안 위협의 이슈가 되고 있는데 연구소 직원이 메일 서비스를 이용하던 중, 피싱 메일의 첨부파일을 실행하여 악성 코드에 감염되었다.
해당 직원의 컴퓨터에는 코로나19 바이러스에 대한 중요한 연구 단서가 있었는데, 해당 파일이 암호화되면서 파일의 내용을 확인할 수 없게 되었는데..

암호화 되어버린 파일을 복구하여 파일의 내용을 읽을 수 있을까?

Nowadays, the world rising a issue what the cyber security threat by COVID-19.
One of the laboratory worker was using mail service.
She clicked on a phishing email that attached malicious code.
Her computer has been important evidence of research about COVID-19.
Unfortunately.. this file also encrypted then We can not open it.

Could you recover that encrypted file for us ?

우선 문제에서 제공된 파일을 다운로드 하면 암호화에 사용된 py파일과 암호화 처리 된 파일이 제공됩니다.

enc.py를 열어보면 다음과 같이 키 값에서 힌트(?) 를 얻을 수 있습니다.

그렇습니다. 키 값은 문제 풀이에 약간의 혼란을 주기 위해 origin key를 base64 인코딩 처리한 키 값입니다.

따라서 해당 키 값을 base64-decode 과정을 거쳐 주면 origin key를 얻을 수 있습니다.

http://enc.rubiya.kr (갓 루비야님)

암호화 된 모듈에 간단히 decode 해주는 로직만 만들어 주면 됩니다.

해당 코드를 실행하면 파일 암호화에 사용된 키 값을 얻을 수 있습니다.

암호화 된 파일을 풀려면 어떤 방식으로 암호화 되었는지 찾아야 하고, openssl 이라는 단서를 가지고

openssl 중 aes256 cbc 방식을 통해 파일 암호화를 풀어주면 됩니다.

openssl aes-256-cbc -d -in the_research_of_covid19.docx.encrypted -out flag.docx

암호화가 풀렸습니다. 파일을 열어 보면 플래그를 획득할 수 있습니다.

'CTF' 카테고리의 다른 글

POX 2020 CTF - VaccineWeb Write Up  (0) 2020.12.14
POX 2020 CTF - Kiban64  (0) 2020.11.25
POX 2020 CTF - Mobile Pentest Write Up  (0) 2020.11.23
Power of XX 2020 Write up  (0) 2020.11.21
DownUnderCTF - Is this pwn or web? write-up  (0) 2020.09.28

+ Recent posts