<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Rainy_Waltz.log</title>
        <link>https://velog.io/</link>
        <description>Rainy Waltz(a_hisa)</description>
        <lastBuildDate>Thu, 09 Apr 2026 03:42:28 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>Rainy_Waltz.log</title>
            <url>https://velog.velcdn.com/images/jinwoo_kim/profile/2b5c7d28-a99f-4b3f-9378-75f133e9ee9c/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. Rainy_Waltz.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/jinwoo_kim" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[Juice Shop] 04. Login Admin]]></title>
            <link>https://velog.io/@jinwoo_kim/Juice-Shop-04.-Login-Admin</link>
            <guid>https://velog.io/@jinwoo_kim/Juice-Shop-04.-Login-Admin</guid>
            <pubDate>Thu, 09 Apr 2026 03:42:28 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jinwoo_kim/post/4bc9ff30-004a-4a2a-a108-3928e4ffd8eb/image.png" alt=""></p>
<p>Juice Shop에는 로그인 창이 있습니다. 여기서 SQL Injection을 시도해 볼 수 있을 것 같습니다.</p>
<p>이메일 창에 <code>&#39; or 1=1 --</code> 이라는 가장 간단한 SQLI 구문을 넣어보겠습니다.
<img src="https://velog.velcdn.com/images/jinwoo_kim/post/20782199-1d05-4424-9a1d-5100a96996b3/image.png" alt=""></p>
<p>로그인을 하니 admin 계정으로 로그인이 되고 Login Admin 문제가 풀렸습니다.</p>
<p><code>&#39; or 1=1 --</code> 구문을 넣었을 때 SQL 쿼리가 아마 <code>SELECT * FROM Users WHERE email = &#39;&#39; or 1=1 --&#39; AND password = &#39;1234&#39;</code> 처럼 변경되었을 것입니다.</p>
<p>여기서 email에는 아무것도 넣지 않았는데 어떻게 admin 계정으로 로그인이 된 것일까요?</p>
<p>이 쿼리는 데이터베이스에게 <strong>&quot;Users 테이블에 있는 모든 회원 정보를 다 가져와!&quot;</strong>라고 명령한 것과 같습니다. 데이터베이스는 수백 명의 회원 정보를 통째로 서버에 던져줍니다.</p>
<p>그런데 주스샵(그리고 대부분의 엉성하게 짜인 웹 서비스)의 로그인 코드는 보통 가져온 회원 정보 목록 중에 첫 번째 사람의 정보로 로그인을 시켜줍니다. 그리고 시스템을 구축할 때 가장 먼저 등록되는 계정은 보통 <code>admin</code>이기 때문에 admin 계정으로 로그인이 된 것입니다.</p>
<p>만약 admin의 이메일 계정 명을 알고 있다면 <code>admin@example.com&#39; or 1=1 --</code> 으로 타겟팅 SQL Injection을 수행할 수도 있습니다.</p>
<p>Coding Challenge를 풀어보겠습니다.
<img src="https://velog.velcdn.com/images/jinwoo_kim/post/1c53336b-2483-4d3a-99d5-44a52a28c598/image.png" alt=""></p>
<p>Find It의 정답은 당연히 SQL 쿼리 문입니다.</p>
<pre><code class="language-sql">models.sequelize.query(`SELECT * FROM Users WHERE email = &#39;${req.body.email || &#39;&#39;}&#39; AND password = &#39;${security.hash(req.body.password || &#39;&#39;)}&#39; AND deletedAt IS NULL`, { model: UserModel, plain: true })</code></pre>
<p>전문은 다음과 같습니다. password는 <code>security.hash</code>로 해시 함수를 적용하여 SQL Injection을 막고 있습니다.</p>
<pre><code>models.sequelize.query(`SELECT * FROM Users WHERE email = $1 AND password = $2 AND deletedAt IS NULL`,
    { bind: [ req.body.email, security.hash(req.body.password) ], model: models.User, plain: true })</code></pre><p>해결 방법은 위와 같이 바인딩 매개변수를 사용해서 데이터베이스가 쿼리 구조를 먼저 파악하고 입력한 데이터를 후에 삽입하여 악의적인 특수 문자도 절대 명령어로 해석하지 않게 하는 것입니다.</p>
<p><img src="https://velog.velcdn.com/images/jinwoo_kim/post/78c9f737-0bd7-4373-81e3-ce132ef45beb/image.png" alt=""></p>
<p>사진처럼 match를 이용하여 필터링하는 방법은 여러 방법으로 우회하여 SQL Injection을 수행할 수 있으므로 바람직한 방법은 아니라고 합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Juice Shop] 03. Error Handling]]></title>
            <link>https://velog.io/@jinwoo_kim/Juice-Shop-03.-Error-Handling</link>
            <guid>https://velog.io/@jinwoo_kim/Juice-Shop-03.-Error-Handling</guid>
            <pubDate>Thu, 09 Apr 2026 02:33:47 GMT</pubDate>
            <description><![CDATA[<p>이 문제도 zap spider가 크롤링하면서 해결한 문제입니다. 문제 설명에는 <code>우아하게 처리되지 않은 에러를 발생시켜라</code> 라고 적혀있습니다. 즉, 에러 핸들러에 등록되지 않은 에러를 발생시키는게 문제의 풀이 방법인 것 같습니다.</p>
<pre><code>docker run -d --rm --name juice-shop-test -p 3001:3000 bkimminich/juice-shop</code></pre><p>어디서 에러를 일으켰는지 확인하기 위해 새 도커 이미지를 만들어줬습니다.</p>
<pre><code>docker logs -f juice-shop-test</code></pre><p>위 명령어로 서버 로그를 띄우고 다시 zap spider를 실행해 보겠습니다.</p>
<p><img src="https://velog.velcdn.com/images/jinwoo_kim/post/97fd6a06-e824-4ca8-914a-25b49dbd7da6/image.png" alt=""></p>
<p><code>Error: Only .md and .pdf files are allowed!</code> 가 Error Handling 문제를 해결한 것 같습니다. </p>
<p>아마도 zap spider가 특정 파일 서버 경로(예: /ftp)를 발견하고, 탐색을 위해 <code>.txt</code>나 <code>.bak</code> 같은 엉뚱한 확장자나 특수문자 등을 무작위로 집어넣은 것 같습니다. </p>
<p>잘못된 확장자가 들어왔을 때 서버에서 &quot;잘못된 요청입니다&quot; 와 같은 예외 처리를 하지 못하고 서버 내부 로직이 붕괴하면서 아래에 Stack Trace를 뱉어냈습니다.</p>
<p><img src="https://velog.velcdn.com/images/jinwoo_kim/post/94c3f175-becc-4f81-a1e2-8772a15d9a9b/image.png" alt=""></p>
<p>3001번으로 접속해 보면 Error Handling이 해결된 것을 확인할 수 있습니다.</p>
<p>+)</p>
<p><img src="https://velog.velcdn.com/images/jinwoo_kim/post/06f7741e-7fd3-4b4c-be61-ff103dc8de8f/image.png" alt=""></p>
<p>/ftp의 bak 파일을 클릭하면 에러를 눈으로 확인할 수도 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Juice Shop] 02. Score Board]]></title>
            <link>https://velog.io/@jinwoo_kim/Juice-Shop-02.-Score-Board</link>
            <guid>https://velog.io/@jinwoo_kim/Juice-Shop-02.-Score-Board</guid>
            <pubDate>Thu, 09 Apr 2026 01:12:55 GMT</pubDate>
            <description><![CDATA[<p>이전에 zap spider로 크롤링을 하다 우연히 Confidential Document 문제를 해결했는데 문제 해결 알림의 <code>Jump to related coding challenge</code> 버튼으로 score board를 찾았습니다.</p>
<p>주소가 <code>localhost:3000/#/score-board</code> 였는데 사이의 <code>/#</code> 때문에 zap에서 안나온 것 같아서 저게 무슨 역할을 하는지 찾아보았습니다.</p>
<p>웹 표준에서 URL의 # 뒤에 붙는 문자열은 서버에 새로운 페이지를 요청하는 용도가 아닙니다. 과거에는 문서 내 특정 위치로 스크롤을 이동시킬 때 썼고, 최근에는 브라우저 내부에서만 화면을 전환(클라이언트 사이드 라우팅)할 때 사용합니다.</p>
<p>따라서 브라우저에 <code>http://localhost:3000/#/score-board</code>를 입력하더라도, 실제로 네트워크를 통해 서버로 날아가는 HTTP 요청은 <code>http://localhost:3000/</code> 뿐입니다. ZAP의 기본 스파이더는 서버와 통신하는 HTTP 패킷을 분석하여 동작하므로, 서버로 전송되지 않는 <code>#</code> 뒷부분은 새로운 경로로 인식하지 않고 무시해 버립니다.</p>
<p><img src="https://velog.velcdn.com/images/jinwoo_kim/post/5382122c-baa6-4653-9379-0787f54c132f/image.png" alt=""></p>
<p>따라서 zap의 기본 스파이더로는 score-board를 찾을 수가 없었던 것입니다.</p>
<p>OWASP Juice Shop은 Angular로 작성되어 있고, Angular의 라우팅 구조는 보통 <code>{path: &#39;score-board&#39;, component: xx}</code> 처럼 생겼습니다.</p>
<p><img src="https://velog.velcdn.com/images/jinwoo_kim/post/04b114d1-7093-4438-be1d-bb16e3581adc/image.png" alt=""></p>
<p>개발자 도구에서 main.js를 찾은 후 <code>path:</code>로 검색하게 되면 <code>score-board</code>를 찾을 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/jinwoo_kim/post/b0aff080-8368-40e1-8e25-4db76f248aa4/image.png" alt=""></p>
<p>이때 zap의 search를 사용하면 웹팩(Webpack) 등의 프론트엔드 빌드 도구에 의해 압축 및 난독화가 적용된 상태이므로 <code>path:</code>는 찾을 수 있지만 <code>score-board</code>는 짧은 변수로 변환되어 찾을 수가 없습니다.</p>
<p><img src="https://velog.velcdn.com/images/jinwoo_kim/post/6ca7ccaa-848e-4469-91ad-88f38a7f7352/image.png" alt=""></p>
<p>Score Board 문제의 코딩 챌린지로 넘어가 보겠습니다. 아까 확인했던 Angular의 라우팅 테이블이 보기로 주어지고 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/jinwoo_kim/post/dae9ce5a-1f40-4aca-a296-405244a3cb46/image.png" alt=""></p>
<p>여기선 당연히 저희가 찾았던 <code>score-board</code> 경로와 component 줄을 선택해 주면 됩니다.</p>
<p><img src="https://velog.velcdn.com/images/jinwoo_kim/post/60de80a0-defc-4f59-b2dd-a44e338c9fc2/image.png" alt=""></p>
<p>Fix It의 정답은 아무런 수정을 하지 않는 것입니다. 처음엔 저도 이게 무슨 말인가 했는데, 아마 score board 페이지는 접근이 제한되어야 하는 페이지가 아니라고 보는 것 같습니다. 난독화나 권한 검사 등을 추가하면 접근해야 할 일반 사용자의 접근성이 떨어지기 때문에 아무런 조치를 하지 않는게 정답인 것 같습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Juice Shop] 01. Confidential Document]]></title>
            <link>https://velog.io/@jinwoo_kim/Juice-Shop-01.-Confidential-Document</link>
            <guid>https://velog.io/@jinwoo_kim/Juice-Shop-01.-Confidential-Document</guid>
            <pubDate>Tue, 07 Apr 2026 04:41:47 GMT</pubDate>
            <description><![CDATA[<p><code>OWASP Juice Shop</code>은 국제 웹 보안 표준 기구인 OWASP에서 모의해킹 및 보안 교육을 목적으로 의도적으로 취약하게 만든 웹 애플리케이션입니다.</p>
<p>단순히 취약한 페이지들의 모음이 아니라, Node.js, Express, Angular 등 현대적인 웹 기술 스택을 사용하여 개발된 완전한 형태의 온라인 과일 주스 쇼핑몰입니다. 사용자 로그인, 상품 검색, 장바구니, 리뷰 작성, 결제 등 실제 서비스와 동일한 비즈니스 로직을 갖추고 있으며, 그 이면에 인젝션(Injection), XSS, 인증 우회, 보안 설정 오류 등 OWASP Top 10을 아우르는 수십 가지의 취약점들이 촘촘하게 숨겨져 있습니다.</p>
<p>단일 취약점의 핵심 원리 파악과 CTF 방식의 문제 해결에 집중된 <code>webhacking.kr</code> 과 달리 실제 모의해킹 업무에 좀 더 가까운 연습을 할 수 있습니다.</p>
<hr>
<p>Docker에서 juice-shop을 다운받은 후 실행하여 접속해보겠습니다. </p>
<p><img src="https://velog.velcdn.com/images/jinwoo_kim/post/fb612422-4410-4ad9-aa29-c037f610b0cb/image.png" alt=""></p>
<p>사진처럼 쇼핑몰같은 사이트가 나옵니다. Account, 검색, 리뷰 등 다양한 기능들이 있습니다. 그 중 <code>Help getting started</code>를 눌러보겠습니다.
<img src="https://velog.velcdn.com/images/jinwoo_kim/post/3712a666-d283-42d4-a6d5-20dfa4af7f1c/image.png" alt=""></p>
<p>Score Board에서 exploit 진행 상황을 확인할 수 있다고 합니다. 따라서 저희의 첫번째 과제는 Score Board를 찾는 것입니다.</p>
<p>Score Board가 주어진 메뉴에는 없기 때문에 zap의 spider로 크롤링을 해보겠습니다. 실제 버그바운티에서는 의도치 않게 DoS 공격이 될 수 있으므로 전송 속도를 조절하거나 gobuster같은 도구로 브루트포싱을 수행 후 의심되는 경로에서 크롤링을 수행합니다.</p>
<p>하지만 Juice Shop은 Docker로 실행하기 때문에 편하게 zap spider를 사용해 보았습니다.</p>
<p><img src="https://velog.velcdn.com/images/jinwoo_kim/post/faf7087f-66b5-471d-92a9-ec58894ceee5/image.png" alt=""></p>
<p>실행을 하니 URL들이 정말 많이 나옵니다. Processed에서 초록불은 분석한 경로들이고, 빨간불은 Flags에 Out of Scope라고 나와있습니다. URL을 보시면 <code>localhost:3000</code>이 아닌 외부 도메인 링크들이기 때문에 무한정 스캔하는 것을 방지하기 위한 것입니다.
<img src="https://velog.velcdn.com/images/jinwoo_kim/post/a24a651c-eca6-4a96-9be7-0c86e01cfa27/image.png" alt=""></p>
<p>zap spider를 실행하니 2개의 Challenge가 해결되었다는 알림이 떴습니다. 아직 Scoreboard를 찾지도 못한 상황에서 의도치 않게 풀려버렸네요.</p>
<p>첫 문제는 <code>Confidential Document (Access a confidential document.)</code> 라고 되어있습니다. 기밀 문서에 접근을 해서 풀렸다고 하는데 아마 크롤링하는 과정에서 자연스럽게 접근이 된 것 같습니다.</p>
<p><img src="https://velog.velcdn.com/images/jinwoo_kim/post/a0967367-544a-489f-bce5-7662f0501c68/image.png" alt=""></p>
<p>URL 중에서 robots.txt가 있는데 /ftp 라는 경로가 Disallow로 숨겨져 있습니다. 일반적으로 사용하는 크롤러들은 웹 표준을 준수하기 때문에 Disallow로 설정된 경로는 수집하지 않습니다. 하지만 zap과 같이 웹 해킹/보안 진단 도구들은 의도적으로 무시할 뿐만 아니라 가장 먼저 공략하는 타겟으로 설정합니다.</p>
<p><img src="https://velog.velcdn.com/images/jinwoo_kim/post/70ce5d6b-0b97-41fe-9aa0-988dda71a9ff/image.png" alt=""></p>
<p>따라서 zap에서는 /ftp의 하위 경로까지 전부 탐색을 하고, 여기서 기밀 문서에 자연스럽게 접근을 하면서 문제가 해결된 것 같습니다.</p>
<p>그리고 <code>Jump to related coding challenge</code>를 누르니 자연스럽게 Score board(<a href="http://localhost:3000/#/score-board?highlightChallenge=directoryListingChallenge)%EB%A1%9C">http://localhost:3000/#/score-board?highlightChallenge=directoryListingChallenge)로</a> 연결이 되었습니다.</p>
<p>주소는 <code>localhost:3000/#/score-board</code> 였네요.
<img src="https://velog.velcdn.com/images/jinwoo_kim/post/639eb7cf-aea6-4ac6-b64d-d441fe6e0738/image.png" alt=""></p>
<p>스코어보드에서는 Challenge 종류, 힌트 뿐만 아니라 Coding Challenge도 존재합니다. Coding Challenge에서는 문제를 유발하는 코드를 찾고, 이를 방어하는 시큐어 코딩(Secure Coding)도 연습해볼 수 있습니다.</p>
<p><img src="https://velog.velcdn.com/images/jinwoo_kim/post/135a377b-68ae-44b7-8483-e965dbf57550/image.png" alt=""></p>
<p>Coding Challenge를 클릭하면 문제점이 존재하는 코드가 나옵니다. Find It은 취약점을 일으키는 코드를 좌측 체크박스로 선택하여 제출하는 문제입니다.</p>
<pre><code class="language-js">app.use(&#39;/ftp&#39;, serveIndexMiddleware, serveIndex(&#39;ftp&#39;, { icons: true }))
app.use(&#39;/ftp(?!/quarantine)/:file&#39;, servePublicFiles())</code></pre>
<p>정답은 이 두 줄입니다. <code>app.use()</code>는 Express.js에서 사용하는 가장 핵심적인 메서드로 특정 URL 경로로 클라이언트의 요청이 들어왔을 때, 서버가 어떤 함수(미들웨어)를 실행할지 연결해 주는 역할을 합니다.</p>
<p>맨 끝의 <code>serveIndex</code>는 Express 환경에서 디렉토리 리스팅 기능을 쉽게 구현하기 위해 따로 설치해서 사용하는 외부 패키지(npm 모듈)입니다. 
<img src="https://velog.velcdn.com/images/jinwoo_kim/post/bdafee2a-deb0-4119-8442-28571ce11ad9/image.png" alt=""></p>
<p>사진과 같이 <code>/ftp</code> 경로로 접속했을 때 사진처럼 디렉토리 리스팅을 가능하게 해줍니다. </p>
<p>첫 번째 줄에서는 디렉토리 리스팅을 허용하여 숨겨진 파일들의 목록을 보여줬다면 두 번째 줄에서는 기밀 파일의 다운로드 및 열람을 실제로 허용시켜줍니다.</p>
<pre><code class="language-js">app.use(&#39;/ftp(?!/quarantine)/:file&#39;, servePublicFiles())</code></pre>
<p><code>&#39;/ftp(?!/quarantine)/:file&#39;</code> 부터 살펴보겠습니다.</p>
<p><code>(?!/quarantine)</code>는 정규표현식의 부정형 전방탐색(Negative Lookahead) 문법으로 앞의 <code>/ftp</code> 패턴에 일치한 후 바로 뒤에 이어지는 문자열이 <code>/quarantine</code>이 아니어야만 조건을 통과시킵니다. 즉, <code>/quarantine</code>에 대한 예외처리를 담당하는 구문입니다.</p>
<p>뒤의 <code>/:file</code>은 Express.js의 경로 파라미터(Path Parameter) 문법입니다. 콜론(:)이 붙으면 그 자리에 들어오는 문자열을 가변적인 변수처럼 취급합니다. Express 서버 내부에서는 req.params.file이라는 코드를 통해 사용자가 어떤 파일을 요청했는지 그 이름을 꺼내 쓸 수 있습니다.</p>
<p>예를 들어 <code>/ftp/acquisitions.md</code>를 요청 URL로 보내보겠습니다. 그럼 <code>/ftp</code>로 시작하고, 바로 뒤의 문자열이 <code>/quarantine</code>이 아니므로 <code>file</code>에는 <code>acquisitions.md</code>가 들어가서 <code>servePublicFiles()</code>로 전달되게 됩니다.</p>
<p><code>servePublicFiles()</code> 함수는 개발자가 작성한 함수로 사용자가 요청한 파일 이름을 받아서, 서버의 실제 하드디스크나 스토리지에서 해당 파일을 찾아 브라우저로 전송해 주는 역할을 합니다.</p>
<p>이 두 줄의 코드를 통해 <code>/ftp</code>에 있는 파일들을 누구나 읽을 수 있게 됩니다.</p>
<p><code>/ftp</code> 뿐만 아니라 <code>/encryptionkeys</code>나 <code>/support/logs</code>도 동일한 취약점이지만, 문제에서 요구하는 답은 <code>/ftp</code> 내부의 파일이므로 2, 3번 라인을 체크 후 제출하면 됩니다.</p>
<p><img src="https://velog.velcdn.com/images/jinwoo_kim/post/8f795b0a-ee41-4c4a-998e-b089318cdd88/image.png" alt=""></p>
<p>Fix It은 취약점을 해결하기 위한 보안 패치를 적용하는 과정으로 Fix 1, 2, 3, 4 중 하나를 선택하여 제출하는 문제입니다.
<img src="https://velog.velcdn.com/images/jinwoo_kim/post/f81e631e-bf4d-4a80-9d35-b80ec498917c/image.png" alt=""></p>
<p>정답은 3번으로 <code>/ftp</code> 폴더를 완전히 없애버리는 것만이 이 데이터 유출 문제를 영구적으로 막을 수 있다고 합니다. </p>
<p>단순히 취약점이 발견된 특정 파일(acquisitions.md)만 예외 처리하거나 땜질식으로 코드를 고치는 것으로는 부족하다는 뜻입니다. <code>/ftp</code>라는 디렉토리 자체가 외부 인터넷에 공개되도록 연결해 둔 것 자체가 근본적인 보안 결함이므로, 화면에 빨간색으로 표시된 라우팅 코드 4줄을 통째로 날려버리는 것이 정답입니다.</p>
<hr>
<p>+) 추가
zap spider 말고 gobuster를 이용한 브루트포싱도 한 번 해보겠습니다.</p>
<p><img src="https://velog.velcdn.com/images/jinwoo_kim/post/f1948a03-f51d-4598-a70f-b6ef489bffed/image.png" alt=""></p>
<p>하던대로 <code>http://localhost:3000</code>으로 실행하면 <code>the server returns a status code that matches the provided options for non existing urls.</code> 라는 에러 로그가 나옵니다.</p>
<p>에러 로그를 보면 Gobuster가 본격적인 스캔을 시작하기 전에 테스트 삼아 절대 존재할 수 없는 무작위 경로(f050eecf...)를 서버에 요청해 보았는데, 서버가 404 Not Found 에러 대신 정상 페이지를 뜻하는 200 OK 상태 코드를 반환했습니다.</p>
<p>Juice Shop과 같은 SPA(Single Page Application) 웹앱은 사용자가 존재하지 않는 경로를 요청하더라도 메인 화면(index.html)으로 라우팅하면서 200 코드를 띄워주는 경우가 많습니다. 이렇게 되면 단어장에 있는 모든 단어가 200으로 응답하게 되므로, 의미 없는 결과가 수천 줄씩 도배되는 것을 막기 위해 Gobuster가 동작을 멈춘 것입니다.</p>
<p>이를 해결하기 위해서는 <code>--exclude-length</code> 옵션을 이요해서 200 코드 크기인 <code>75002</code>라는 크기를 가진 응답은 무시하도록 설정해주면 됩니다.</p>
<p><img src="https://velog.velcdn.com/images/jinwoo_kim/post/096a14e1-01cc-4c74-b319-daed0608bd75/image.png" alt=""></p>
<p>옵션을 준 뒤 실행하면 zap spider로 찾았던 <code>ftp</code>, <code>robots.txt</code> 같은 링크들을 동일하게 찾을 수 있습니다.</p>
<hr>
<p>Juice Shop에 대한 첫 글이다 보니 소개를 포함해서 글이 좀 길어졌습니다. 금방 끝날 줄 알고 가볍게 시작했는데 문제 풀이가 끝나는 것 보다 취직하는게 더 빠를 수도 있겠다는 생각이 드네요;;</p>
<p>아무튼 모든 문제를 다 풀 때까지 열심히 달려보겠습니다. 이어서 Scoreboard 풀이로 오겠습니다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[webhacking.kr] old-42]]></title>
            <link>https://velog.io/@jinwoo_kim/webhacking.kr-old-42</link>
            <guid>https://velog.io/@jinwoo_kim/webhacking.kr-old-42</guid>
            <pubDate>Tue, 07 Apr 2026 01:41:13 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jinwoo_kim/post/82e4e363-f441-414b-a704-0984ccfc65b0/image.png" alt="">
test.txt 파일과 flag.docx 파일의 다운로드 링크가 있는 페이지가 나옵니다. flag.docx의 다운로드 링크를 누르면 Access Denied라는 alert가 나오고 test.txt는 다운로드가 됩니다.</p>
<p>코드를 확인해 보겠습니다.</p>
<pre><code class="language-html">&lt;table border=1 align=center width=300&gt;
&lt;tr&gt;&lt;td width=50&gt;no&lt;/td&gt;&lt;td&gt;subject&lt;/td&gt;&lt;td&gt;file&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;test&lt;/td&gt;&lt;td&gt;test.txt [&lt;a href=?down=dGVzdC50eHQ=&gt;download&lt;/a&gt;]&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;read me&lt;/td&gt;&lt;td&gt;flag.docx [&lt;a href=javascript:alert(&quot;Access%20Denied&quot;)&gt;download&lt;/a&gt;]&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;</code></pre>
<p>test 부분 download의 하이퍼링크를 보면 <code>?down=dGVzdC50eHQ=</code> 로 연결되고, flag.docx 부분의 download는 alert를 띄우는 걸 볼 수 있습니다.</p>
<p>여기서 GET으로 down에 <code>dGVzdC50eHQ=</code> 라는 값을 주면 test.txt를 다운받을 수 있습니다. 암호화된 값인 것 같아서 드림핵에서 제공하는 <a href="https://tools.dreamhack.games/cyberchef">cyberchef</a> 에서 이것저것 값을 넣어서 확인해봤습니다.</p>
<p><img src="https://velog.velcdn.com/images/jinwoo_kim/post/8ca91276-d523-4713-a22d-533e9dd0b6a2/image.png" alt=""></p>
<p>plaintext는 아마 <code>test</code> 또는 <code>test.txt</code>일 것 같아서 두 값에 여러 연산을 하던 중 <code>test.txt</code>를 base64 인코딩 한 결과가 아까 down의 값과 동일한 것을 발견하였습니다.</p>
<p><img src="https://velog.velcdn.com/images/jinwoo_kim/post/85b7eece-7793-4de5-9285-008d536aff57/image.png" alt=""></p>
<p>저희가 필요한 파일은 <code>flag.docx</code> 이므로 동일하게 base64 인코딩을 해준 후 GET으로 전달하면 플래그를 다운받을 수 있습니다.</p>
<p>정리를 하면서 생각해보니 인코딩한 값일 경우 디코딩을 하는데 자원이 거의 소모되지 않으므로 여러 방법으로 디코딩을 먼저 해보는게 효율적인 것 같습니다. 그리고 해시 함수의 경우 출력 문자열의 길이가 고정이므로 여기서는 시도를 해 볼 필요가 없었던 것 같습니다.</p>
<hr>
<p><img src="https://velog.velcdn.com/images/jinwoo_kim/post/e07b56ec-3543-4606-88f6-0373481855a1/image.png" alt=""></p>
<p>검색하면서 알게 된 기능인데 <code>Magic</code> 이라는 기능에 미지의 문자열을 넣으면, 자체적으로 문자열의 패턴과 엔트로피를 분석하여 어떤 인코딩이나 암호화가 수행되었는지 자동으로 추천해준다고 합니다. </p>
<p>다만 단방향 해시 함수나 개발자의 커스텀 로직이 존재하는 경우 풀어줄 수 없기 때문에 처음에 간단하게 인코딩된 값인지 알아보는데 사용하는게 좋을 것 같습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[webhacking.kr] old-43]]></title>
            <link>https://velog.io/@jinwoo_kim/webhacking.kr-old-43</link>
            <guid>https://velog.io/@jinwoo_kim/webhacking.kr-old-43</guid>
            <pubDate>Tue, 07 Apr 2026 00:59:57 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jinwoo_kim/post/b9937268-db39-4269-a193-db50e02482ab/image.png" alt="">
webshell을 업로드해서 플래그를 찾는 문제인 것 같습니다.
<img src="https://velog.velcdn.com/images/jinwoo_kim/post/f3c290cd-83ca-4890-bd16-d50457cc9b7e/image.png" alt=""></p>
<p>칼리 리눅스에 여러 종류의 웹쉘이 기본적으로 포함되어 있습니다. php 디렉터리에 있는 웹쉘만 간단히 설명드리겠습니다.</p>
<table>
<thead>
<tr>
<th align="left">파일명</th>
<th align="left">유형</th>
<th align="left">주요 특징</th>
</tr>
</thead>
<tbody><tr>
<td align="left"><strong>php-reverse-shell.php</strong></td>
<td align="left">리버스 쉘</td>
<td align="left">타겟 서버가 공격자에게 연결을 시도하게 하여 대화형 Bash 쉘을 획득할 때 사용합니다. 가장 널리 쓰이는 도구입니다.</td>
</tr>
<tr>
<td align="left"><strong>simple-backdoor.php</strong></td>
<td align="left">기본 백도어</td>
<td align="left">URL 파라미터(GET)를 통해 간단한 시스템 명령어를 실행합니다. 코드가 매우 짧고 단순한 것이 특징입니다.</td>
</tr>
<tr>
<td align="left"><strong>php-backdoor.php</strong></td>
<td align="left">일반 백도어</td>
<td align="left">명령어를 입력할 수 있는 웹 UI(HTML 폼)를 제공하여 브라우저상에서 편리하게 제어할 수 있습니다.</td>
</tr>
<tr>
<td align="left"><strong>qsd-php-backdoor.php</strong></td>
<td align="left">우회형 백도어</td>
<td align="left">전송 데이터를 인코딩하거나 난독화하여 웹 방화벽(WAF)이나 탐지 시스템을 우회하려 할 때 유리합니다.</td>
</tr>
<tr>
<td align="left"><strong>php-findsock-shell.php</strong></td>
<td align="left">소켓 탈취 쉘</td>
<td align="left">인바운드/아웃바운드가 모두 차단된 엄격한 방화벽 환경에서 기존 HTTP 소켓 세션을 가로채어 쉘을 연결하는 고급 기법입니다.</td>
</tr>
</tbody></table>
<p>저는 가장 단순한 <code>simple-backdoor.php</code>를 사용해 보겠습니다. </p>
<p><img src="https://velog.velcdn.com/images/jinwoo_kim/post/38a69238-289e-4305-b216-ecb73128a514/image.png" alt=""></p>
<p>업로드를 하니 wrong type이라는 텍스트가 나옵니다. 아마 php 타입의 파일을 필터링하는 것 같습니다. php와 마찬가지로 <code>.c</code>와 <code>.py</code> 파일도 필터링을 수행합니다.</p>
<p>POST에서는 파일의 타입을 Content-Type으로 전송합니다. php의 경우 <code>application/octet-stream</code>이라는 Content-Type을 사용합니다. 
<img src="https://velog.velcdn.com/images/jinwoo_kim/post/66cf8906-ddca-4c2a-baae-a3c3d9c74e58/image.png" alt=""></p>
<p>이를 Burpsuite를 사용하여 Content-Type을 php가 아닌 사진처럼 이미지 파일이라고 위조하여 전송하게 되면 필터링 로직을 우회하여 웹쉘을 업로드할 수 있습니다.
<img src="https://velog.velcdn.com/images/jinwoo_kim/post/7fb52155-43d3-4f3a-9151-8e784bde8934/image.png" alt=""></p>
<p>웹쉘이 정상적으로 업로드가 된 모습입니다.
<img src="https://velog.velcdn.com/images/jinwoo_kim/post/1a0e4fd4-f2db-4bbf-81ac-fca82935358d/image.png" alt="">
앞에서 설명드린대로 <code>simple-backdoor.php</code>는 GET 파라미터를 통해 동작하기 때문에 뒤에 <code>?cmd=cat /flag</code>를 붙여주면 플래그를 얻을 수 있습니다.</p>
<p>실전에서는 WAF에서 더 복잡하게 필터링을 하기 때문에 지금처럼 헤더 변경만으로는 우회를 할 수 없습니다. 그래도 old-43은 파일 업로드 공격을 배울 때 가장 기초가 되는 기법을 연습할 수 있는 좋은 문제라고 생각합니다.</p>
<hr>
<p>+)추가
<img src="https://velog.velcdn.com/images/jinwoo_kim/post/ce01fef4-adaf-410d-b274-1ba75d1ea451/image.png" alt="">
php-backdoor.php 실행 화면입니다. simple-backdoor.php와 다르게 웹 인터페이스 화면이 존재합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[버그바운티 및 모의해킹을 위한 정보 수집 파이프라인]]></title>
            <link>https://velog.io/@jinwoo_kim/%EB%B2%84%EA%B7%B8%EB%B0%94%EC%9A%B4%ED%8B%B0-%EB%B0%8F-%EB%AA%A8%EC%9D%98%ED%95%B4%ED%82%B9%EC%9D%84-%EC%9C%84%ED%95%9C-%EC%A0%95%EB%B3%B4-%EC%88%98%EC%A7%91-%ED%8C%8C%EC%9D%B4%ED%94%84%EB%9D%BC%EC%9D%B8</link>
            <guid>https://velog.io/@jinwoo_kim/%EB%B2%84%EA%B7%B8%EB%B0%94%EC%9A%B4%ED%8B%B0-%EB%B0%8F-%EB%AA%A8%EC%9D%98%ED%95%B4%ED%82%B9%EC%9D%84-%EC%9C%84%ED%95%9C-%EC%A0%95%EB%B3%B4-%EC%88%98%EC%A7%91-%ED%8C%8C%EC%9D%B4%ED%94%84%EB%9D%BC%EC%9D%B8</guid>
            <pubDate>Mon, 06 Apr 2026 15:58:26 GMT</pubDate>
            <description><![CDATA[<p>KISA 아카데미에서 진행하는 버그헌팅 실습 훈련 중급 과정 강의를 듣던 중 여러가지 타겟 정보 수집 방법이 나와서 실제로 버그바운티를 진행할 때 도움이 조금이라도 되도록 정리해 보았습니다.</p>
<h1 id="0-왜-정보-수집이-필수적인가">0. 왜 정보 수집이 필수적인가?</h1>
<p>성공적인 모의해킹과 버그바운티의 성패는 &#39;얼마나 고도화된 해킹 기술을 아느냐&#39;가 아니라 &#39;타겟에 대해 얼마나 깊고 넓게 파악하고 있느냐&#39;에서 판가름 난다고 합니다. 거대한 시스템 전체를 지켜야 하는 방어자에 비해, 공격자는 단 하나의 치명적인 빈틈만을 찾으면 되기 때문입니다.</p>
<p>보안 솔루션으로 견고하게 지켜지고 있는 메인 서비스만 공격하는 것은 굳게 닫힌 성벽에 돌을 던지는 것과 같습니다. 따라서 본격적인 취약점 분석에 앞서 넓은 시야로 타겟의 인프라 전반을 파악하고, 관리자의 시선에서 벗어나 숨겨진 서버나 방치된 클라우드 자산 등 공격 표면을 극대화하는 &quot;정보 수집 단계&quot;가 반드시 선행되어야 합니다.</p>
<h1 id="1-수동-탐색-및-osint">1. 수동 탐색 및 OSINT</h1>
<p>타겟 서버에 직접적인 접속 흔적을 남기지 않고, 외부에 이미 공개된 소스들을 활용해 최대한 많은 정보를 긁어모으는 안전한 정찰 단계입니다.</p>
<h2 id="직접-탐색">직접 탐색</h2>
<p>브라우저를 통해 웹사이트를 직접 돌아다니며 주요 기능, 로그인 로직, 파라미터 구조 등을 파악하고 공격 가능성이 있는 포인트를 메모합니다.</p>
<h2 id="구글-해킹">구글 해킹</h2>
<p>구글 검색 Operator를 이용하여 실수로 노출된 관리자 페이지, 에러 로그, 백업 파일 등을 찾아내는 방법입니다.</p>
<p>아래와 같은 Operator들이 주로 사용됩니다.</p>
<table>
<thead>
<tr>
<th align="left">검색 연산자</th>
<th align="left">기능 설명</th>
<th align="left">활용 예시</th>
</tr>
</thead>
<tbody><tr>
<td align="left">site:</td>
<td align="left">특정 도메인이나 사이트 내에서만 검색 결과를 제한합니다.</td>
<td align="left"><code>site:target.com</code> (해당 도메인의 노출된 모든 페이지 검색)</td>
</tr>
<tr>
<td align="left">inurl:</td>
<td align="left">URL 경로(주소)에 특정 키워드가 포함된 페이지를 찾습니다.</td>
<td align="left"><code>site:target.com inurl:admin</code> (관리자 전용 페이지 및 로그인 경로 탐색)</td>
</tr>
<tr>
<td align="left">intitle:</td>
<td align="left">웹 페이지 제목(Title 태그)에 특정 키워드가 포함된 결과를 찾습니다.</td>
<td align="left"><code>intitle:&quot;index of&quot;</code> (디렉토리 리스팅이 활성화된 취약한 서버 탐색)</td>
</tr>
<tr>
<td align="left">intext:</td>
<td align="left">웹 페이지 본문 내용에 특정 키워드가 포함된 페이지를 찾습니다.</td>
<td align="left"><code>site:target.com intext:&quot;DB_PASSWORD&quot;</code> (본문에 민감한 정보나 설정값이 노출된 페이지 탐색)</td>
</tr>
<tr>
<td align="left">ext: (또는 filetype:)</td>
<td align="left">특정 확장자를 가진 파일 문서만 검색합니다.</td>
<td align="left"><code>site:target.com ext:sql OR ext:bak</code> (방치된 데이터베이스 덤프나 소스코드 백업 파일 탐색)</td>
</tr>
<tr>
<td align="left">&quot; &quot; (큰따옴표)</td>
<td align="left">입력한 문자열이나 띄어쓰기와 정확하게 일치하는 결과만 검색합니다.</td>
<td align="left"><code>&quot;syntax error in SQL statement&quot;</code> (특정 프레임워크나 DB의 에러 메시지 누출 확인)</td>
</tr>
<tr>
<td align="left">- (하이픈/빼기)</td>
<td align="left">검색 결과에서 특정 키워드나 다른 연산자의 결과를 제외합니다.</td>
<td align="left"><code>site:target.com -www</code> (메인 도메인을 제외한 숨겨진 서브도메인 탐색)</td>
</tr>
<tr>
<td align="left">* (와일드카드)</td>
<td align="left">임의의 단어나 문자를 대체하여 검색의 범위를 넓힙니다.</td>
<td align="left"><code>site:*.target.com</code> (타겟과 연관된 모든 1차 서브도메인 탐색)</td>
</tr>
<tr>
<td align="left">cache:</td>
<td align="left">구글 서버에 저장된 해당 웹 페이지의 과거 캐시(백업) 버전을 봅니다.</td>
<td align="left"><code>cache:target.com/api/v1</code> (현재는 수정되었거나 삭제된 페이지의 이전 내용 확인)</td>
</tr>
</tbody></table>
<p><a href="https://www.exploit-db.com/google-hacking-database">GHDB(Google Hacking Database)</a>에서 검색 연산자를 이용한 구글 해킹 패턴들을 찾아볼 수 있습니다.</p>
<h2 id="과거-웹-기록-조회wayback-machine">과거 웹 기록 조회(Wayback Machine)</h2>
<p>현재 서버의 메인 페이지에서는 삭제되었으나 여전히 접속 가능한 구버전 엔드포인트나 숨겨진 경로 리스트를 수집할 수 있습니다.</p>
<p><code>waybackurls</code>나 <code>gau</code> 같은 도구를 사용합니다. 도구들의 사용법까지 하나하나 설명하기에는 양이 너무 많고, config는 ai가 매우 잘 알려주기 때문에 여기선 그냥 넘어가겠습니다. 차후 OWASP Juice Shop이나 다른 실습을 할 때 사용을 하게 되면 그때 추가로 글을 작성해 보겠습니다.</p>
<h2 id="깃허브-repository-조사">깃허브 Repository 조사</h2>
<p>타겟 기업의 오픈 소스 저장소를 검색합니다. 개발자가 실수로 커밋한 API 키, DB 비밀번호, 내부 설정 파일 등 치명적인 정보 유출을 확인합니다.</p>
<p><code>Trufflehog</code>나 <code>Gitrob</code>을 사용하여 검색할 수 있습니다. 주로 사용하는 검색 키워드는 <code>key</code>, <code>secret</code>, <code>password</code> 등이 있습니다.</p>
<h1 id="2-자산-및-인프라-식별">2. 자산 및 인프라 식별</h1>
<p>메인 도메인 외에 보안 관리가 소홀할 확률이 높은 방치된 서버와 인프라를 찾아내어 공격의 범위를 넓힙니다.</p>
<h2 id="하위-도메인-조사">하위 도메인 조사</h2>
<p><code>Subfinder</code>나 <code>Amass</code> 같은 도구를 통해 메인 도메인에 연결된 수많은 개발용, 사내용 하위 도메인을 빠짐없이 나열합니다.</p>
<h2 id="포트-스캐닝">포트 스캐닝</h2>
<p>식별된 하위 도메인과 연결된 IP 대역을 대상으로 <code>Nmap</code>같은 스캐닝 도구를 실행합니다. 80(HTTP), 443(HTTPS) 웹 포트 외에도 22(SSH), 21(FTP) 등 외부에 불필요하게 열려 있는 포트와 구동 중인 서비스를 식별합니다.</p>
<h1 id="3-기술-스택-식별타겟-정밀-분석">3. 기술 스택 식별(타겟 정밀 분석)</h1>
<p>앞서 수집된 서버들이 어떤 프로그래밍 언어와 프레임워크로 만들어졌는지 파악하여, 후속 공격에 사용할 맞춤형 도구와 페이로드를 결정하는 단계입니다.</p>
<h2 id="기술-스택-프로파일링">기술 스택 프로파일링</h2>
<p><code>Wappalyzer</code>나 <code>WhatWeb</code>을 사용하여 웹 서버(Nginx, Apache), 언어(PHP, Java), 프레임워크(Spring, React), CMS(WordPress)의 종류와 구체적인 버전을 식별합니다. 특정 버전에서 발생하는 알려진 취약점(CVE)을 찾기 위한 핵심 기초 자료가 됩니다.</p>
<h1 id="4-콘텐츠-발굴숨겨진-경로-탐색">4. 콘텐츠 발굴(숨겨진 경로 탐색)</h1>
<p>웹 서버 내부에 사용자가 정상적인 클릭으로는 도달할 수 없는 숨겨진 통로를 찾는 단계입니다. 효율성을 위해 가벼운 브루트포싱을 선행한 후 심층 스파이더링을 진행합니다.</p>
<h2 id="경로-브루트포싱">경로 브루트포싱</h2>
<p><a href="https://velog.io/@jinwoo_kim/Gobuster-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0">Gobuster</a>를 이용해 방대한 단어가 포함된 사전(Wordlist) 파일을 서버에 빠르게 대입해 봅니다. 화면에 링크가 걸려 있지 않은 /admin, /config, /.env, /test.php 등의 디렉토리와 파일을 단시간에 찾아냅니다.</p>
<h2 id="스파이더링">스파이더링</h2>
<p>브루트포싱으로 발견한 숨겨진 페이지들과 메인 페이지를 기점으로, OWASP ZAP 등을 활용해 웹사이트 내부의 모든 연결된 링크를 타고 들어가며 전체 페이지 구조를 지도로 그립니다.</p>
<h1 id="5-심층-조사">5. 심층 조사</h1>
<p>현대적인 인프라 환경에서 가장 빈번하게 대형 보안 사고를 유발하는 클라우드 취약점 포인트를 최종 점검합니다.</p>
<h2 id="클라우드-스토리지-조사">클라우드 스토리지 조사</h2>
<p>타겟 기업이 AWS 등 퍼블릭 클라우드를 사용한다면, <code>구글 해킹</code>이나 <code>GrayhatWarfare</code>를 통해 공개적으로 접근 권한이 열려 있는 S3 버킷이 존재하는지 탐색합니다. 해당 버킷 내부에 민감한 고객 데이터나 중요 소스코드가 방치되어 있는지 확인합니다.</p>
<hr>
<p>강의에서 배운 내용과 추가로 찾아본 내용을 정리해 보았습니다. 버그바운티 경험이 거의 없는 사람이 쓴 글이므로 참고 용도로만 봐주시면 감사하겠습니다.</p>
<p>앞으로 좀 더 많은 경험을 쌓은 뒤 경험을 토대로 글의 내용을 보충하거나 새 글을 다시 작성해 보겠습니다. 감사합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[webhacking.kr] old-51]]></title>
            <link>https://velog.io/@jinwoo_kim/webhacking.kr-old-51</link>
            <guid>https://velog.io/@jinwoo_kim/webhacking.kr-old-51</guid>
            <pubDate>Mon, 06 Apr 2026 05:06:59 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jinwoo_kim/post/814409e2-8519-4f42-9f2a-c5f50cb02724/image.png" alt=""></p>
<p>문제에 접속하면 Admin page와 로그인 창이 나옵니다. 아무 계정으로 로그인을 해보면 Wrong이 출력됩니다. 우하단에 view_source가 있으니 코드를 한 번 보겠습니다.</p>
<pre><code class="language-php">&lt;?php
  if($_POST[&#39;id&#39;] &amp;&amp; $_POST[&#39;pw&#39;]){
    $db = dbconnect();
    $input_id = addslashes($_POST[&#39;id&#39;]);
    $input_pw = md5($_POST[&#39;pw&#39;],true);
    $result = mysqli_fetch_array(mysqli_query($db,&quot;select id from chall51 where id=&#39;{$input_id}&#39; and pw=&#39;{$input_pw}&#39;&quot;));
    if($result[&#39;id&#39;]) solve(51);
    if(!$result[&#39;id&#39;]) echo &quot;&lt;center&gt;&lt;font color=green&gt;&lt;h1&gt;Wrong&lt;/h1&gt;&lt;/font&gt;&lt;/center&gt;&quot;;
  }
?&gt;</code></pre>
<p>입력 폼을 통해 POST로 id와 pw를 받습니다. 
id는 <code>addslashes</code>라는 함수를 실행하여 <code>input_id</code>에 저장합니다. 여기서 <code>addslashes</code> 함수는 PHP에서 문자열 내의 특정 문자(<code>&#39;</code>, <code>&quot;</code>, <code>\</code>, <code>NULL</code>) 앞에 백슬래시를 붙여서 코드가 아닌 데이터로 인식시키는 함수입니다.
pw는 <code>md5</code> 해시 함수를 통해 암호화하여 <code>input_pw</code>에 저장합니다. </p>
<p>id와 pw 둘 다 <code>addslashes</code>와 <code>md5</code>를 통해 SQL Injection을 방지하고 있는 것 처럼 보입니다. 하지만 <code>md5($_POST[&#39;pw&#39;], true)</code> 에서 <code>true</code>라는 옵션에 주목해야 합니다. md5 함수의 두 번째 파라미터인 <code>$raw_output</code>에 <code>true</code>를 주게 되면 16바이트 길이의 원시 바이너리(Raw Binary) 데이터를 그대로 반환합니다. 이게 문제가 되는 이유는 DB에서 이 바이너리 데이터를 일반적인 ASCII 문자열로 해석하려고 시도하기 때문에 해시 함수 값이 <code>&#39;or&#39;</code> 혹은 <code>&#39;=&#39;</code>으로 나오는 데이터를 찾아 SQL Injection을 수행할 수 있습니다.</p>
<pre><code class="language-python">import hashlib
import random
import string
import time

def find_md5_sqli_payload():
    print(&quot;🎯 MD5 SQL 인젝션 페이로드 탐색을 시작합니다...&quot;)

    attempts = 0
    start_time = time.time()

    # 랜덤으로 생성할 문자셋 (알파벳 대소문자 + 숫자)
    charset = string.ascii_letters + string.digits

    while True:
        # 10자리 길이의 랜덤 문자열 생성
        raw_string = &#39;&#39;.join(random.choices(charset, k=10))

        # md5 해시 후 16바이트 원시 바이너리(digest) 추출
        hash_bytes = hashlib.md5(raw_string.encode(&#39;utf-8&#39;)).digest()

        # ---------------------------------------------------
        # 1. &#39;=&#39; 패턴 검사
        # ---------------------------------------------------
        if b&quot;&#39;=&#39;&quot; in hash_bytes:
            idx = hash_bytes.find(b&quot;&#39;=&#39;&quot;)
            # &#39;=&#39; 패턴(길이 3) 뒤에 오는 문자가 있는지 확인
            if idx + 3 &lt; len(hash_bytes):
                next_char = hash_bytes[idx + 3]
                # 아스키코드 49(&#39;1&#39;) ~ 57(&#39;9&#39;)가 아니어야 0으로 자동 형변환됨
                if next_char &lt; 49 or next_char &gt; 57:
                    print(f&quot;\n[🔥 찾았습니다! &#39;=&#39; 패턴 성공]&quot;)
                    print(f&quot; - 입력할 평문(Payload) : {raw_string}&quot;)
                    print(f&quot; - 생성된 바이너리 Hex  : {hash_bytes.hex()}&quot;)
                    break

        # ---------------------------------------------------
        # 2. &#39;or&#39; 패턴 검사
        # ---------------------------------------------------
        if b&quot;&#39;or&#39;&quot; in hash_bytes:
            idx = hash_bytes.find(b&quot;&#39;or&#39;&quot;)
            # &#39;or&#39; 패턴(길이 4) 뒤에 오는 문자가 있는지 확인
            if idx + 4 &lt; len(hash_bytes):
                next_char = hash_bytes[idx + 4]
                # 아스키코드 49(&#39;1&#39;) ~ 57(&#39;9&#39;) 사이여야 참(True)으로 인식됨
                if 49 &lt;= next_char &lt;= 57:
                    print(f&quot;\n[🔥 찾았습니다! &#39;or&#39; 패턴 성공]&quot;)
                    print(f&quot; - 입력할 평문(Payload) : {raw_string}&quot;)
                    print(f&quot; - 생성된 바이너리 Hex  : {hash_bytes.hex()}&quot;)
                    break

        attempts += 1
        # 100만 번 시도할 때마다 진행 상황 출력
        if attempts % 1000000 == 0:
            elapsed = time.time() - start_time
            print(f&quot;... {attempts:,}번 시도 중 ({elapsed:.1f}초 경과) ...&quot;)

if __name__ == &#39;__main__&#39;:
    find_md5_sqli_payload()</code></pre>
<p>조건에 맞는 문자열을 찾는 python 코드입니다. md5 해시 후 16바이트 원시 바이너리 값에 <code>&#39;=&#39;</code> 또는 <code>&#39;or&#39;</code> 가 포함되는 문자열을 찾아줍니다.</p>
<p><code>&#39;=&#39;</code> 패턴 검사에서 <code>&#39;=&#39;</code> 이후 1~9 사이가 아닌지 검사를 하는 이유는 MySQL에서는 문자열인 &#39;뒷부분&#39;을 숫자 0과 비교하기 위해, 문자열을 숫자로 강제로 변환하려고 시도합니다. 만약 문자열이 <code>123abc</code>처럼 숫자로 시작한다면 이를 숫자 <code>123</code>으로 변환됩니다. 이 경우 <code>0=123</code>이 되어 결과가 False가 됩니다. 하지만 숫자 외에 문자나 특수기호로 시작된다면 <code>0</code>으로 변환해버리기 때문에 <code>select id from chall51 where id=&#39;{$input_id}&#39; and 0=0</code> 이 되어 id만 실제로 존재한다면 True가 됩니다.</p>
<p><code>&#39;or&#39;</code>의 경우는 <code>&#39;=&#39;</code> 패턴과 반대로 or 이후가 0이 아니어야 하므로 <code>&#39;or&#39;</code> 이후 1~9사이가 되어야 합니다.
<img src="https://velog.velcdn.com/images/jinwoo_kim/post/ed911259-3565-4fdc-8f87-6419ad5e0139/image.png" alt=""></p>
<p><code>&#39;=&#39;</code> 패턴으로 성공하였으므로 ID에는 존재하는 값인 <code>admin</code>을, 비밀번호에는 <code>t7BYyDUdw8</code>를 넣으면 문제를 해결할 수 있습니다.
<img src="https://velog.velcdn.com/images/jinwoo_kim/post/00099f8d-4f25-4e6e-bf8d-b7b625514075/image.png" alt="">
<code>&#39;or&#39;</code> 패턴은 조건이 훨씬 까다로워서 그런지 시간이 오래 걸리는 모습입니다. <code>&#39;or&#39;</code> 패턴 중에는 <code>ffifdyop</code> 라는 알려진 우회 문자열이 있으므로 비밀번호에 <code>ffifdyop</code>를 넣어서도 문제를 해결할 수 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[webhacking.kr] old-19]]></title>
            <link>https://velog.io/@jinwoo_kim/webhacking.kr-old-19-avqler32</link>
            <guid>https://velog.io/@jinwoo_kim/webhacking.kr-old-19-avqler32</guid>
            <pubDate>Thu, 02 Apr 2026 05:21:22 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jinwoo_kim/post/9eec053b-c8a0-4165-b4c7-3b11353916ac/image.png" alt="">
접속하면 id 제출 창이 나옵니다. 기본 값이 admin인 걸 봐선 admin으로 로그인하는 것이 문제의 목표인 것 같습니다. 그냥 제출을 눌러보면 당연히 <code>you are not admin</code>이라는 문장이 나옵니다.</p>
<p>그럼 guest로 로그인을 한 번 해보겠습니다. 
<img src="https://velog.velcdn.com/images/jinwoo_kim/post/e54ab498-361a-4516-9c61-26fb8c23dd02/image.png" alt="">
hello guest라는 화면이 나오고 logout 버튼을 누르기 전까지는 계속 유지되는 걸 보니 쿠키로 id 값이 유지되고 있는 것 같습니다.
쿠키를 확인해 보니 <code>userid</code> 라는 쿠키에 <code>YjJmNWZmNDc0MzY2NzFiNmU1MzNkOGRjMzYxNDg0NWQ3Yjc3NGVmZmU0YTM0OWM2ZGQ4MmFkNGY0ZjIxZDM0Y2UxNjcxNzk3YzUyZTE1Zjc2MzM4MGI0NWU4NDFlYzMyMDNjN2MwYWNlMzk1ZDgwMTgyZGIwN2FlMmMzMGYwMzRlMzU4ZWZhNDg5ZjU4MDYyZjEwZGQ3MzE2YjY1NjQ5ZQ%3D%3D</code> 라는 값이 저장되어 있습니다. 해시 함수로 암호화되어 있는 것 같아서 gemini에게 평문과 암호문을 주고 해시 함수를 찾아달라고 해보겠습니다.</p>
<p>userid의 값은 <code>guest</code>를 한 글자씩 MD5 해시 함수로 암호화하고 base64 -&gt; URL 인코딩을 수행한 값이라고 합니다. 알고리즘을 알았으니 <code>admin</code>을 똑같은 과정으로 암호화한 후 값을 <code>userid</code>에 넣으면 해결할 수 있습니다.</p>
<p>값을 <code>MGNjMTc1YjljMGYxYjZhODMxYzM5OWUyNjk3NzI2NjE4Mjc3ZTA5MTBkNzUwMTk1YjQ0ODc5NzYxNmUwOTFhZDZmOGY1NzcxNTA5MGRhMjYzMjQ1Mzk4OGQ5YTE1MDFiODY1YzBjMGI0YWIwZTA2M2U1Y2FhMzM4N2MxYTg3NDE3YjhiOTY1YWQ0YmNhMGU0MWFiNTFkZTdiMzEzNjNhMQ==</code>로 변경한 후 새로고침을 하면 문제를 해결할 수 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[webhacking.kr] old-10]]></title>
            <link>https://velog.io/@jinwoo_kim/webhacking.kr-old-19-k90cnx3n</link>
            <guid>https://velog.io/@jinwoo_kim/webhacking.kr-old-19-k90cnx3n</guid>
            <pubDate>Thu, 02 Apr 2026 03:40:35 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jinwoo_kim/post/b71ca3da-2da5-474f-b429-0eb836ac0894/image.png" alt="">
접속하니 달리기 트랙? 같은 화면이 나옵니다. 오른쪽 끝에 Goal이 있고 왼쪽 O에 마우스를 올리면 <code>yOu</code> 라는 글자로 바뀝니다. 클릭을 하면 앞으로 조금씩 가긴 하는데 정확한 동작을 확인하기 위해 코드를 확인해 보겠습니다.</p>
<pre><code>&lt;a id=hackme style=&quot;position:relative;left:0;top:0&quot; onclick=&quot;this.style.left=parseInt(this.style.left,10)+1+&#39;px&#39;;if(this.style.left==&#39;1600px&#39;)this.href=&#39;?go=&#39;+this.style.left&quot; onmouseover=this.innerHTML=&#39;yOu&#39; onmouseout=this.innerHTML=&#39;O&#39;&gt;O&lt;/a&gt;&lt;br&gt;
&lt;font style=&quot;position:relative;left:1600;top:0&quot; color=gold&gt;|&lt;br&gt;|&lt;br&gt;|&lt;br&gt;|&lt;br&gt;Goal&lt;/font&gt;</code></pre><p>동작을 간단히 보면 <code>hackme</code>를 한 번 클릭할 때마다 left로 1px씩 이동하고, 1600px에 도달하면 <code>?go=1600px</code>로 이동합니다. 목표는 1600px를 이동해서 goal에 도착하는 거 같고, 가장 쉬운 방법은 아무래도 직접 1600번 클릭을 하는 방법입니다. 오토마우스나 마사지건 같은 걸 쓰면 금방 해결할 수 있으니 이것도 괜찮은 방법이라고 생각은 합니다.</p>
<p>문제의 의도대로 풀어보면 html은 클라이언트 측에서 개발자 도구로 수정이 가능하므로 <code>hackme</code>의 left값을 1599로 수장하고 한 번 클릭하면 해결이 가능합니다. </p>
<p>position 말고 onclick의 조건을 바꿔도 봤는데 이때는 <code>no hack</code>이라는 글이 출력되는 걸 봐서는 스크립트 수정은 막혀있는 것 같습니다.</p>
<p><img src="https://velog.velcdn.com/images/jinwoo_kim/post/177992a3-d0d4-49cd-8e34-ce0ae70e1e4b/image.png" alt=""></p>
<p>이렇게 position 값을 1599로 수정하고 한 번 클릭을 해서 위치를 1600으로 바꿔주면 문제가 해결됩니다.<img src="https://velog.velcdn.com/images/jinwoo_kim/post/2ad5b389-9928-43af-bdbe-a84bd7e742b1/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[webhacking.kr] old-41]]></title>
            <link>https://velog.io/@jinwoo_kim/webhacking.kr-old-41</link>
            <guid>https://velog.io/@jinwoo_kim/webhacking.kr-old-41</guid>
            <pubDate>Thu, 02 Apr 2026 03:20:30 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jinwoo_kim/post/1b04d487-2f58-4bc6-8fe4-6944bcb52c47/image.png" alt="">
old-41번입니다. 문제에 접속하니 파일 업로드 폼이 보이는 걸 봐서 파일 업로드에 관한 취약점이 있을 것 같습니다. </p>
<pre><code class="language-php">&lt;?php
  include &quot;../../config.php&quot;;
  include &quot;./inc.php&quot;;
  if($_GET[&#39;view_source&#39;]) view_source();
  error_reporting(E_ALL);
  ini_set(&quot;display_errors&quot;, 1);
?&gt;&lt;html&gt;
&lt;head&gt;
&lt;title&gt;Challenge 41&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;?php
  if(isset($_FILES[&#39;up&#39;]) &amp;&amp; $_FILES[&#39;up&#39;]){
    $fn = $_FILES[&#39;up&#39;][&#39;name&#39;];
    $fn = str_replace(&quot;.&quot;,&quot;&quot;,$fn);
    $fn = str_replace(&quot;&lt;&quot;,&quot;&quot;,$fn);
    $fn = str_replace(&quot;&gt;&quot;,&quot;&quot;,$fn);
    $fn = str_replace(&quot;/&quot;,&quot;&quot;,$fn);

    $cp = $_FILES[&#39;up&#39;][&#39;tmp_name&#39;];
    copy($cp,&quot;./{$upload_dir}/{$fn}&quot;);
    $f = @fopen(&quot;./{$upload_dir}/{$fn}&quot;,&quot;w&quot;);
    @fwrite($f,$flag);
    @fclose($f);
    echo(&quot;Done~&quot;);
  }
?&gt;</code></pre>
<p>문제 코드입니다. 가장 중요하게 봐야 할 부분은 <code>error_reporting(E_ALL); ini_set(&quot;display_errors&quot;, 1);</code> 입니다. 이 설정으로 인해 발생하는 모든 PHP 에러가 화면에 출력되게 됩니다.</p>
<p>이제 파일 업로드 부분을 보겠습니다. 파일 명으로 스크립트가 실행되는 것을 막기 위해 <code>.</code>, <code>&lt;</code>, <code>&gt;</code>, <code>/</code>을 공백으로 replace합니다. 그 후 <code>./{upload_dir}</code> 이라는 디렉터리에 올린 파일 이름과 동일한 파일을 생성한 뒤 그 파일에 플래그를 작성합니다.</p>
<p>하지만 <code>upload_dir</code> 이름이 뭔지 모르니 플래그가 만들어진 파일에 접속할 방법이 없습니다. 이때 PHP 에러 로그를 사용할 수 있습니다. 파일을 업로드할 때 어떻게든 에러를 발생시키면 파일의 경로가 에러 로그에 출력이 되고 로그를 통해 <code>upload_dir</code>을 확인할 수 있습니다.</p>
<p>에러를 일으키기 위해서는 어떻게 해야 할까요? 여러 방법이 있겠지만 여기선 파일명의 최대 길이를 사용해 보겠습니다. 대부분의 운영체제에서 최대 길이는 255자이므로 이를 넘어가는 파일명을 가지는 파일을 업로드하면 에러를 일으킬 수 있습니다.</p>
<p>파일명 제한이 걸리는 건 윈도우도 마찬가지이므로 burpsuite를 사용해 패킷을 수정하겠습니다. 
<img src="https://velog.velcdn.com/images/jinwoo_kim/post/dce7342c-fdc4-4cfd-a893-5d05f9c9cc1e/image.png" alt=""></p>
<p>패킷의 filename이 지금은 <code>aa.txt</code>로 되어있는데 이 값을 256자 이상으로 설정하면 에러가 발생합니다.
<img src="https://velog.velcdn.com/images/jinwoo_kim/post/f887562b-6394-4d6d-9b47-f03e84d72eb0/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/jinwoo_kim/post/890c09ef-6a25-4695-97b9-78323281962d/image.png" alt=""></p>
<p>적당히 값을 길게 하고 Forward를 하면 에러와 함께 <code>upload_dir</code>이 나타납니다. <code>/4b0e87fef7b5e8ba83894970c9806042e5d6ec9a</code> 디렉터리는 공용이기 때문에 새로운 파일을 올린 뒤 그 경로로 접속해도 되지만 다른 사람들이 올렸을만한 <code>test</code> 같은 경로로 접속해도 플래그를 얻을 수 있습니다.
<img src="https://velog.velcdn.com/images/jinwoo_kim/post/f8c56a29-213c-4d1f-8fc1-c3c9dbcb33d2/image.png" alt=""></p>
<p>접속하면 다음과 같이 플래그를 획득할 수  있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[webhacking.kr] old-12]]></title>
            <link>https://velog.io/@jinwoo_kim/webhacking.kr-old-12</link>
            <guid>https://velog.io/@jinwoo_kim/webhacking.kr-old-12</guid>
            <pubDate>Thu, 02 Apr 2026 02:37:04 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jinwoo_kim/post/abcd6274-d034-4aca-9da2-633d1b56706b/image.png" alt="">
문제에 접속하니 <code>javascript challenge</code> 라는 문장 하나만 나와있습니다. javascript challenge라 하니 js 코드를 확인해 보면 <code>ﾟωﾟﾉ= /｀ｍ´）ﾉ ~┻━┻   //*´∇｀*/ [&#39;_&#39;]; o=(ﾟｰﾟ)</code> 처럼 되게 귀여운(?) 이모티콘들이 잔뜩 있습니다.</p>
<p>아무래도 난독화를 해놓은 거 같은데 어떻게 한 건지 몰라서 우리의 친구 Gemini에게 풀어달라고 해봤습니다. 그랬더니 AAEncode 라는 방식으로 인코딩을 한 결과라고 알려줬습니다. 디코딩할 수 있는 <a href="https://a.tools/Tool.php?Id=174">사이트</a>가 있어 디코딩을 해줬더니 아래 코드가 나왓습니다.</p>
<pre><code class="language-js">var enco=&#39;&#39;;
var enco2=126;
var enco3=33;
var ck=document.URL.substr(document.URL.indexOf(&#39;=&#39;));
for(i=1;i&lt;122;i++){
  enco=enco+String.fromCharCode(i,0);
}
function enco_(x){
  return enco.charCodeAt(x);
}
if(ck==&quot;=&quot;+String.fromCharCode(enco_(240))+String.fromCharCode(enco_(220))+String.fromCharCode(enco_(232))+String.fromCharCode(enco_(192))+String.fromCharCode(enco_(226))+String.fromCharCode(enco_(200))+String.fromCharCode(enco_(204))+String.fromCharCode(enco_(222-2))+String.fromCharCode(enco_(198))+&quot;~~~~~~&quot;+String.fromCharCode(enco2)+String.fromCharCode(enco3)){
  location.href=&quot;./&quot;+ck.replace(&quot;=&quot;,&quot;&quot;)+&quot;.php&quot;;
};</code></pre>
<p>ck값을 계산(제미나이가)하면 <code>=youaregod~~~~~~~!</code>이 나옵니다. <code>https://webhacking.kr/challenge/code-3/youaregod~~~~~~~!.php</code> 로 접속하면 문제를 해결할 수 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[webhacking.kr] old-36]]></title>
            <link>https://velog.io/@jinwoo_kim/webhacking.kr-old-33-355tas6p</link>
            <guid>https://velog.io/@jinwoo_kim/webhacking.kr-old-33-355tas6p</guid>
            <pubDate>Thu, 02 Apr 2026 01:42:15 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jinwoo_kim/post/38a451d9-39d7-4dae-b6d5-20a88cf42f92/image.png" alt="">
접속하면 index.php 파일을 vi 에디터로 편집하던 도중 전원이 꺼져서 소스 코드가 사라졌다는 얘기를 하고 있습니다. 파일을 복구하는 것이 이 문제의 목적인거 같습니다.</p>
<p>vi 에디터를 사용하고 있었다는 내용이 가장 중요한 힌트입니다. vi 에디터에서는 비정상적으로 에디터가 종료되었을 때 백업을 위해 <code>.swp</code> 파일을 생성합니다. </p>
<p>실제로는 편집 시작 시 <code>.파일명.swp</code> 이라는 임시 파일을 생성했다가 정상 종료 시 삭제하는 동작을 수행합니다. 따라서 비정상적으로 종료되었다면 <code>.파일명.swp</code>이라는 파일이 존재합니다.</p>
<p>index.php의 백업 파일인 <code>.index.php.swp</code>으로 이동하면 스왑 파일이 다운로드되고, 파일에서 플래그를 얻을 수 있습니다.</p>
<pre><code>b0VIM 8.0              섷  root                                    webhacking.kr                           /var/www/html/challenge/bonus-8/index.php                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         
U3210    #&quot;! U                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 tp                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        ad  ?  ?            ?  ?  ?                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     ??&gt;   $flag = &quot;FLAG{what_about_the_nano_editor?}&quot;; &lt;?php </code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[webhacking.kr] old-25]]></title>
            <link>https://velog.io/@jinwoo_kim/webhacking.kr-old-25</link>
            <guid>https://velog.io/@jinwoo_kim/webhacking.kr-old-25</guid>
            <pubDate>Thu, 02 Apr 2026 01:10:18 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jinwoo_kim/post/f12fc6d0-3397-4ce7-ae7a-22973c9c9672/image.png" alt="">
접속하면 어딘가 익숙한 화면이 반겨줍니다. 리눅스에서 <code>ls -al</code>을 실행한 결과랑 동일한 창인거 같습니다. 파일을 보면 flag.php, hello.php, index.php 라는 파일이 존재합니다.
url이 <code>http://webhacking.kr:10001/?file=hello</code> 이므로 file에 flag를 넣어 이동해 보겠습니다.
<img src="https://velog.velcdn.com/images/jinwoo_kim/post/0bc172ee-cb0c-40b9-9a96-82acb319e716/image.png" alt="">
그러면 <code>FLAG is in the code</code> 라는 글이 나옵니다. flag.php 라는 파일 안에 구하려는 플래그가 존재한다는 얘긴거 같은데 그럼 저 파일을 확인하려면 어떻게 해야할까요? <code>?file=hello</code> 처럼 파일 경로를 파라미터로 직접 받는 경우 LFI 취약점이 존재할 수 있습니다. <a href="https://velog.io/@jinwoo_kim/php-wrapper">php Wrapper</a>와 <a href="https://velog.io/@jinwoo_kim/LFI-%EC%B7%A8%EC%95%BD%EC%A0%90-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0-96oh2vh8">LFI 취약점</a>에 관한 내용은 연결된 링크를 참조해주세요. </p>
<p>이 문제도 GET을 통해 파일 경로를 파라미터로 직접 받고 있습니다. flag.php 파일 안에 플래그가 존재한다고 했으니 php wrapper를 사용해 파일에 들어있는 플래그를 확인해 보겠습니다.</p>
<p>원본 소스 코드를 읽을 때 <code>php://filter</code> wrapper를 사용합니다. 
<img src="https://velog.velcdn.com/images/jinwoo_kim/post/20f1e607-59d4-46c2-bdc7-8de577ddcc0a/image.png" alt="">
<code>?file=</code> 이후에 <code>php://filter/read=convert.base64-encode/resource=flag</code> 를 넣으면 base64로 인코딩 된 소스 코드를 얻을 수 있습니다.</p>
<pre><code class="language-php">&lt;?php
  echo &quot;FLAG is in the code&quot;;
  $flag = &quot;FLAG{this_is_your_first_flag}&quot;;
?&gt;</code></pre>
<p>디코딩을 한 결과는 다음과 같습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[LFI 취약점 알아보기]]></title>
            <link>https://velog.io/@jinwoo_kim/LFI-%EC%B7%A8%EC%95%BD%EC%A0%90-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0-96oh2vh8</link>
            <guid>https://velog.io/@jinwoo_kim/LFI-%EC%B7%A8%EC%95%BD%EC%A0%90-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0-96oh2vh8</guid>
            <pubDate>Tue, 31 Mar 2026 06:46:28 GMT</pubDate>
            <description><![CDATA[<h2 id="1-lfi-취약점이란">1. LFI 취약점이란</h2>
<p>LFI(Local File Inclusion) 취약점은 웹 애플리케이션이 사용자 입력값을 제대로 검증하지 않아, 공격자가 서버 내부의 민감한 파일을 읽어오거나 임의의 코드를 실행할 수 있게 되는 웹 보안 취약점입니다.</p>
<h2 id="2-동작-원리">2. 동작 원리</h2>
<h3 id="기본-동작">기본 동작</h3>
<p>정상적인 웹사이트가 파라미터를 통해 페이지를 불러온다고 가정해 보겠습니다.</p>
<ul>
<li>정상적인 요청 : <code>http://example.com/index.php?page=example.php</code> (example.php 파일을 불러옴)</li>
</ul>
<p>만약 웹 애플리케이션이 page에 들어오는 값을 아무런 검증 없이 그냥 사용한다면 경로 조작 문자(<code>../</code>)를 삽입하여 웹 루트 디렉터리를 벗어날 수 있습니다.</p>
<h3 id="php-wrapper와-결합">php wrapper와 결합</h3>
<p>이러한 취약점이 php Wrapper와 결합되면 단순한 파일 읽기를 넘어, 필터링 우회나 원격 코드 실행 등 공격의 파급력이 극대화될 수 있습니다.</p>
<p>1. <code>php://filter</code> (소스 코드 유출 및 필터링 우회)</p>
<p>가장 널리 쓰이는 Wrapper입니다. 일반적으로 <code>include()</code> 함수로 PHP 파일을 불러오면 서버에서 해당 코드가 실행된 결과만 화면에 출력됩니다. 하지만 공격자가 웹 애플리케이션 설정 파일 등의 &#39;원본 소스 코드&#39;를 읽고 싶을 때 <code>php://filter</code>를 사용합니다.</p>
<ul>
<li>공격 원리: 서버의 파일을 읽어올 때 Base64 등의 인코딩 필터를 적용하여 읽어옵니다. 코드가 인코딩된 문자열로 반환되기 때문에 PHP 엔진이 이를 코드로 인식하지 않고 단순 문자열로 취급하여 실행하지 않을 채 화면에 그대로 출력하게 됩니다. 공격자는 출력된 Base64 값을 디코딩하여 원본 소스 코드를 획득합니다.</li>
<li>공격 예시: <code>http://example.com/index.php?page=php://filter/read=convert.base64-encode/resource=example.php</code></li>
</ul>
<p>2. <code>php://input</code> (원격 코드 실행 - RCE)
HTTP 요청의 Body 데이터를 직접 읽어올 수 있는 Wrapper입니다. 이 방법은 서버의 <code>php.ini</code> 설정에서 <code>allow_url_include</code> 옵션이 On으로 설정되어 있으면 가능합니다.</p>
<ul>
<li>공격 원리: URL 파라미터에는 <code>php://input</code>을 지정하고, HTTP POST 요청의 본문 영역에 악성 PHP 코드를 삽입하여 전송합니다. 서버는 POST로 전달된 악성 코드를 마치 로컬 파일의 내용인 것처럼 인식하고 <code>include()</code>하여 실행하게 됩니다.</li>
<li>공격 예시<ul>
<li>URL: <code>http://example.com/index.php?lage=php://input</code></li>
<li>body: <code>&lt;?php system(&#39;cat /etc/passwd&#39;); ?&gt;</code></li>
</ul>
</li>
</ul>
<p>예시에서는 <code>cat /etc/passwd</code>를 사용했으나 웹쉘을 실행시켜 관리자 권한을 획득할 수도 있습니다. </p>
<p>3. <code>data://</code> (원격 코드 실행 - RCE)
<code>php://input</code>과 동일하게 원격 코드 실행을 수행할 수 있습니다. 이 역시 <code>allow_url_include</code> 옵션이 On이어야 동작합니다.</p>
<ul>
<li>공격 원리: 별도의 파일을 서버에 업로드할 필요 없이 URL 파라미터 내에 평문이나 Base64로 인코딩된 악성 코드를 직접 주입하여 실행시킵니다. </li>
<li>공격 예시: <code>&lt;?php system(&#39;id&#39;); ?&gt;</code>코드를 Base64로 인코딩하여 삽입
<code>http://example.com/index.php?page=data://text/plain;base64,PD9waHAgc3lzdGVtKCdpZCcpOyA/Pg==</code></li>
</ul>
<p>평문이 아닌 Base64로 인코딩해서 전송하는 이유는 WAF 우회 목적도 존재합니다. 하지만 주된 이유는 URL 문법 충돌 방지 및 안전한 전송을 위해서입니다. 특수 문자들을 URL 파라미터에 그대로 넣게 되면 유실되거나 잘못 해석될 수 있어 에러를 일으킬 수 있기 때문입니다.</p>
<p>그럼 앞에서 본 <code>php://input</code>과 <code>data://</code>를 둘 다 알아야 하는 이유가 있을까요? 둘 다 <code>allow_url_include = On</code>이 되어 있어야 한다는 공통된 전제 조건이 필요합니다. 하지만 악성 데이터를 서버로 전달하는 방식(HTTP Body vs URL)이 다르기 때문에 POST Body와 URL의 보안 설정에 따라 각 방식의 성공 여부가 갈리게 됩니다.</p>
<p>따라서 <code>php.ini</code>에서 <code>allow_url_include</code>가 On으로 설정되어 있으면 <code>php://input</code>과 <code>data://</code> 두 가지 Wrapper를 다 사용해보는 것이 좋은 접근 방식입니다.</p>
<p>4. <code>zip://</code> 및 <code>phar://</code> (압축 파일을 이용한 실행)
공격자가 서버에 이미지 파일(.jpg) 등으로 위장한 압축 파일(.zip)을 업로드할 수 있는 환경에서 사용됩니다.</p>
<ul>
<li>공격 원리: 악성 PHP 코드가 담긴 파일을 압축한 뒤, 확장자를 허용하는 이미지 포맷으로 변경하여 서버에 업로드합니다. 이후 LFI 취약점을 통해 <code>zip://</code> 또는 <code>phar://</code> Wrapper를 사용하여 해당 파일 내부의 악성 코드를 직접 지정해 실행시킵니다. 앞의 <code>php://input</code>이나 <code>data://</code> 방식과는 다르게 <code>allow_url_include</code>가 Off 상태에서도 동작할 수 있어 매우 위협적입니다.</li>
<li>공격 예시: <code>http://example.com/index.php?page=zip://uploads/image.jpg#shell.php</code></li>
</ul>
<p>두 방식의 차이점을 간단히 보면 아래와 같습니다.</p>
<table>
<thead>
<tr>
<th align="left">구분</th>
<th align="left">zip://</th>
<th align="left">phar://</th>
</tr>
</thead>
<tbody><tr>
<td align="left">구분자 (Delimiter)</td>
<td align="left"># (반드시 %23으로 URL 인코딩 필요)</td>
<td align="left">/ (인코딩 불필요)</td>
</tr>
<tr>
<td align="left">경로 지정 방식</td>
<td align="left">주로 절대 경로 요구</td>
<td align="left">상대 경로 사용 가능 (유연함)</td>
</tr>
<tr>
<td align="left">지원 압축 포맷</td>
<td align="left">ZIP 전용</td>
<td align="left">PHAR, ZIP, TAR 등 다양함</td>
</tr>
<tr>
<td align="left">추가 취약점 연계 (중요)</td>
<td align="left">해당 없음</td>
<td align="left">PHP Object Injection (Deserialization) 연계 가능</td>
</tr>
</tbody></table>
<p>단순히 차이점만 보면 <code>phar://</code>을 두고 <code>zip://</code>을 쓸 이유가 전혀 없어보입니다. 하지만 필터링 로직이나 보안 설정 과정에서 <code>zip://</code>만 통과가 되는 경우가 존재할 수 있기 때문에 <code>phar://</code>으로 먼저 시도를 해본 뒤 안된다면 <code>zip://</code>으로도 시도를 해보는 것이 좋은 방법이라고 생각됩니다.</p>
<hr>
<p>앞 게시글인 php wrapper에 이어 LFI 취약점과 두 방식을 결합한 취약점에 대해서 알아봤습니다. 이제 이 방식을 이용한 워게임 풀이(<a href="http://webhacking.kr:10001/?file=hello">webhacking.kr old-25</a>)를 다음 포스트에서 해보겠습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[php wrapper 알아보기]]></title>
            <link>https://velog.io/@jinwoo_kim/php-wrapper</link>
            <guid>https://velog.io/@jinwoo_kim/php-wrapper</guid>
            <pubDate>Tue, 31 Mar 2026 02:53:46 GMT</pubDate>
            <description><![CDATA[<p>webhacking.kr에서 문제를 풀다 php wrapper와 LFI를 이용하여 푸는 문제(old-25)가 있어서 개념을 한 번 정리해 보려고 합니다.</p>
<h1 id="php-wrapper">php wrapper</h1>
<p>php에서 래퍼(Wrapper)란, 다양한 종류의 데이터 소스(파일, 압축 파일, 웹 주소 등)를 다룰 때 일관된 방법으로 접근할 수 있도록 감싸주는 인터페이스 또는 핸들러를 의미합니다.</p>
<h2 id="wrapper의-역할">wrapper의 역할</h2>
<p>PHP에는 파일을 읽거나 쓸 때 사용하는 <code>fopen()</code>, <code>include()</code> 같은 함수들이 있습니다. 원래 이 함수들은 서버(로컬)에 있는 일반적인 파일을 다루기 위해 만들어 졌습니다.</p>
<p>하지만 개발을 하다 보면 인터넷에 있는 다른 웹 페이지의 내용, 혹은 메모리상의 데이터를 읽어오고 싶을 때가 있습니다. 이때 데이터 소스마다 복잡하게 다른 함수를 새로 만드는 대신, 앞에 특정 프로토콜을 붙여주면, 그 규칙에 맞는 &#39;래퍼&#39;가 알아서 데이터를 가져와 일반 파일처럼 읽을 수 있게 만들어 주는 시스템이 스트림 래퍼(Stream Wrapper) 입니다.</p>
<h2 id="대표적인-php-내장-wrapper">대표적인 php 내장 wrapper</h2>
<p>자세한 내용은 php 공식 웹사이트에서 확인하실 수 있습니다. (<a href="https://www.php.net/manual/en/wrappers.php">https://www.php.net/manual/en/wrappers.php</a>)</p>
<table>
<thead>
<tr>
<th align="left">이름</th>
<th align="left">기능</th>
</tr>
</thead>
<tbody><tr>
<td align="left">file://</td>
<td align="left">로컬 파일 시스템 접근</td>
</tr>
<tr>
<td align="left">http://</td>
<td align="left">HTTP(s) URL 접근</td>
</tr>
<tr>
<td align="left">ftp://</td>
<td align="left">FTP(s) URL 접근</td>
</tr>
<tr>
<td align="left">php://</td>
<td align="left">다양한 입출력(I/O) 스트림 접근</td>
</tr>
<tr>
<td align="left">zlib://</td>
<td align="left">압축 스트림</td>
</tr>
<tr>
<td align="left">data://</td>
<td align="left">데이터 (RFC 2397)</td>
</tr>
<tr>
<td align="left">glob://</td>
<td align="left">패턴과 일치하는 경로명 검색</td>
</tr>
<tr>
<td align="left">phar://</td>
<td align="left">PHP 아카이브</td>
</tr>
<tr>
<td align="left">ssh2://</td>
<td align="left">보안 셸 2 연결</td>
</tr>
<tr>
<td align="left">rar://</td>
<td align="left">RAR 압축 파일 접근</td>
</tr>
<tr>
<td align="left">ogg://</td>
<td align="left">오디오 스트림 접근</td>
</tr>
<tr>
<td align="left">expect://</td>
<td align="left">프로세스 상호작용 스트림</td>
</tr>
</tbody></table>
<h2 id="실제-사용-예시">실제 사용 예시</h2>
<h3 id="1-phpinput-rest-api-맟-웹훅-데이터-수신">1. php://input (REST API 맟 웹훅 데이터 수신)</h3>
<p>가장 실무에서 빈번하게 쓰이는 형태입니다. 클라이언트(Vue, React 등 프론트엔드)나 외부 결제 서비스(웹훅)가 application/json 형태로 데이터를 POST 전송할 때, PHP의 기본 $_POST 배열로는 이 데이터를 읽을 수 없습니다. 이때 가공되지 않은 순수 요청 본문(Raw Body)을 읽어오기 위해 사용합니다.</p>
<pre><code class="language-php">// 외부에서 전송된 JSON 데이터를 읽어서 배열로 변환
$rawData = file_get_contents(&#39;php://input&#39;);
$requestData = json_decode($rawData, true);

echo &quot;요청받은 사용자 ID: &quot; . $requestData[&#39;user_id&#39;];</code></pre>
<h3 id="2-phpoutput-대용량-csv-파일-실시간-생성-및-다운로드">2. php://output (대용량 CSV 파일 실시간 생성 및 다운로드)</h3>
<p>서버의 하드 디스크에 파일을 굳이 저장하지 않고, 메모리상에서 생성한 데이터를 사용자의 브라우저로 즉시 스트리밍(다운로드)할 때 사용합니다. 서버의 디스크 용량을 절약하고 처리 속도를 크게 높일 수 있습니다.</p>
<pre><code class="language-php">header(&#39;Content-Type: text/csv; charset=utf-8&#39;);
header(&#39;Content-Disposition: attachment; filename=&quot;report.csv&quot;&#39;);

// 디스크가 아닌 브라우저 출력 스트림을 직접 엽니다.
$output = fopen(&#39;php://output&#39;, &#39;w&#39;);

// 데이터를 출력 스트림에 직접 씁니다 (바로 다운로드 됨)
fputcsv($output, [&#39;이름&#39;, &#39;이메일&#39;, &#39;가입일&#39;]);
fputcsv($output, [&#39;홍길동&#39;, &#39;hong@test.com&#39;, &#39;2026-03-31&#39;]);

fclose($output);</code></pre>
<h3 id="3-phar-애플리케이션-패키징-및-배포">3. phar:// (애플리케이션 패키징 및 배포)</h3>
<p>수십, 수백 개의 PHP 파일로 이루어진 라이브러리나 프로그램을 하나의 실행 가능한 .phar (PHP Archive) 파일로 묶어서 배포할 때 씁니다.</p>
<pre><code class="language-php">// phar 파일 압축을 풀지 않고도 내부의 설정 파일을 바로 읽음
$config = include &#39;phar://my-app.phar/config/database.php&#39;;</code></pre>
<hr>
<p>TCP Wrapper를 이용한 LFI 취약점은 다음 포스트에서 이어서 정리해 보겠습니다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[webhacking.kr] old-38]]></title>
            <link>https://velog.io/@jinwoo_kim/webhacking.kr-old-38</link>
            <guid>https://velog.io/@jinwoo_kim/webhacking.kr-old-38</guid>
            <pubDate>Tue, 31 Mar 2026 01:35:18 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jinwoo_kim/post/37a32c2f-ea05-493a-86d6-729bb150612d/image.png" alt="">
문제에 접속하면 LOG INJECTION 이라는 제목과 제출 폼이 하나 있습니다. 이렇게 봐서는 잘 모르겠으니 코드를 확인해 보겠습니다.</p>
<pre><code>&lt;html&gt;
&lt;head&gt;
&lt;title&gt;Challenge 38&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;h1&gt;LOG INJECTION&lt;/h1&gt;
&lt;form method=post action=index.php&gt;
&lt;input type=text name=id size=20&gt;
&lt;input type=submit value=&#39;Login&#39;&gt;
&lt;/form&gt;
&lt;!-- &lt;a href=admin.php&gt;admin page&lt;/a&gt; --&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre><p>html 코드를 확인해 보면 <code>admin.php</code>로 이동할 수 있는 admin page라는 하이퍼링크가 주석 처리 되어있는 걸 확인할 수 있습니다.
<img src="https://velog.velcdn.com/images/jinwoo_kim/post/f22eac1e-5a93-42af-83ac-021a4053b939/image.png" alt=""></p>
<p>만약 주석이 없어도 Gobuster(<a href="https://velog.io/@jinwoo_kim/Gobuster-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0">https://velog.io/@jinwoo_kim/Gobuster-%EC%95%8C%EC%95%84%EB%B3%B4%EA%B8%B0</a>) 라는 도구를 통해 위 사진처럼 admin.php를 찾을 수 있습니다.</p>
<p>이제 admin.php로 접속을 해보겠습니다.
<img src="https://velog.velcdn.com/images/jinwoo_kim/post/f23b1bdb-c1af-4a7a-b0ed-533f4defcf01/image.png" alt="">
접속을 하면 <code>You must logged as &quot;admin&quot;</code> 이라는 문구가 나옵니다. index.php로 돌아가서 login 창에 admin을 입력하면 <code>you are not admin</code>이라는 문구가 나옵니다.</p>
<p>admin.php의 이름이 log viewer이므로 일단 다른 이름으로 로그인을 한 후 다시 확인해 보겠습니다. guest로 로그인을 한 후 다시 접속해보면 아래 사진처럼 로그가 나오게 됩니다.
<img src="https://velog.velcdn.com/images/jinwoo_kim/post/a34c8833-3fda-4f54-81a7-c1097f482cc9/image.png" alt="">
그렇다면 로그에 IP:admin 이라는 로그가 존재하면 사용자를 로그인으로 인식할 수도 있을 거 같습니다. 하지만 input type이 <code>text</code>인 경우 한 줄만 입력받기 때문에 개행 문자가 삽입되지 않습니다. 따라서 <code>input</code>을 <code>textarea</code>로 변경한 후 줄을 띄워서 새로운 로그를 입력하면 해결할 수 있습니다.</p>
<pre><code>guest
Your IP:admin</code></pre><p><img src="https://velog.velcdn.com/images/jinwoo_kim/post/660b9ca9-c478-4b39-8054-b68f10f7b863/image.png" alt="">
사진처럼 textarea로 변경한 뒤 값을 입력해주면 풀리게 됩니다.
<img src="https://velog.velcdn.com/images/jinwoo_kim/post/9a4f0679-3965-4eb5-be49-330f00c4ba4d/image.png" alt=""></p>
<p>textarea 말고 burp suite를 이용하여 POST 패킷을 직접 수정해 조작하는 방법도 있습니다. burp suite를 실행한 뒤 proxy &gt; Open browser를 눌러 패킷을 가로챌 브라우저를 실행합니다.
<img src="https://velog.velcdn.com/images/jinwoo_kim/post/0f06ebae-57ff-4c3b-82a3-42b29cbb98ad/image.png" alt=""></p>
<p>문제에 접속한 뒤 사진처럼 Intercept를 on으로 바꾸고 로그인 버튼을 눌러보겠습니다.
<img src="blob:https://velog.io/02e078a7-e625-454d-bd73-4749e527ff08" alt="업로드중.."></p>
<p><img src="blob:https://velog.io/dc48cbc4-f923-4c60-bd13-0a81a03e0849" alt="업로드중.."></p>
<p>그럼 사진과 같이 패킷이 잡히고 Request 탭에서 패킷을 수정할 수가 있습니다. 아래의 id=aa 부분에 <code>%0d%0aYourIP:admin</code> 을 추가하면 동일하게 풀 수 있습니다. 여기서 %0d%0a는 CSRF(개행 문자)입니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[webhacking.kr] old-27]]></title>
            <link>https://velog.io/@jinwoo_kim/webhacking.kr-old-27</link>
            <guid>https://velog.io/@jinwoo_kim/webhacking.kr-old-27</guid>
            <pubDate>Tue, 31 Mar 2026 01:08:04 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jinwoo_kim/post/4ff4d8ae-8148-4e26-935d-7b1a6a2e7e79/image.png" alt="">
문제에 접속하면 SQLI 문제라는 걸 알려주고 있습니다. 제출 탭에 sqli 쿼리문을 작성해서 넣으면 되는 문제 같습니다. 어떤 구문을 넣어야 하는지 확인하기 위해 view-source 탭으로 이동해 보겠습니다.</p>
<pre><code>&lt;?php
  if($_GET[&#39;no&#39;]){
  $db = dbconnect();
  if(preg_match(&quot;/#|select|\(| |limit|=|0x/i&quot;,$_GET[&#39;no&#39;])) exit(&quot;no hack&quot;);
  $r=mysqli_fetch_array(mysqli_query($db,&quot;select id from chall27 where id=&#39;guest&#39; and no=({$_GET[&#39;no&#39;]})&quot;)) or die(&quot;query error&quot;);
  if($r[&#39;id&#39;]==&quot;guest&quot;) echo(&quot;guest&quot;);
  if($r[&#39;id&#39;]==&quot;admin&quot;) solve(27); // admin&#39;s no = 2
}
?&gt;</code></pre><p>조건은 r[id] 값을 admin으로 설정하는 것입니다. preg_match를 통해 <code>#</code>, <code>select</code>, <code></code>, <code>limit</code>, <code>=</code>, <code>0x</code>를 필터링하고 있습니다. 그리고 no= 뒤에 <code>()</code>로 묶여있기 때문에 괄호 안에 그냥 구문을 입력하면 참인 경우 앞의 <code>id=&#39;guest&#39;</code>에 의해 guest가 반환되게 됩니다.</p>
<p>따라서 닫는 괄호를 통해 no=(0) 이런 식으로 닫은 후 기존에 존재하던 뒤의 괄호는 주석 처리를 하여 해결할 수 있을 것 같습니다. 쿼리문을 작성해 보면 <code>0) or id=admin --</code> 으로 문제를 풀 수 있습니다.</p>
<p>이제 preg_match를 우회하기 위해 공백과 <code>=</code>를 대체해주면 됩니다. 공백은 <code>%09(탭)</code>, 등호는 <code>like</code> 구문으로 대체해주면 최종 결과는 <code>0)%09or%09id%09like%09&#39;admin&#39;%09--%09</code> 가 됩니다.</p>
<p>이걸 입력 폼에 넣으면 %09를 특수 문자가 아닌 그냥 문자열로 인식하기 때문에 url의 <code>?no=</code>를 통해 전달해주면 됩니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[webhacking.kr] old-39]]></title>
            <link>https://velog.io/@jinwoo_kim/Webhacking.kr-old-39</link>
            <guid>https://velog.io/@jinwoo_kim/Webhacking.kr-old-39</guid>
            <pubDate>Tue, 31 Mar 2026 00:40:58 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jinwoo_kim/post/2a088a1e-fb36-4512-a80f-51fe6ac16025/image.png" alt="">
문제에 접속하면 위와 같이 제출 창과 view-source 링크가 있습니다. 코드를 먼저 확인해 보겠습니다.</p>
<pre><code>&lt;?php
  $db = dbconnect();
  if($_POST[&#39;id&#39;]){
    $_POST[&#39;id&#39;] = str_replace(&quot;\\&quot;,&quot;&quot;,$_POST[&#39;id&#39;]);
    $_POST[&#39;id&#39;] = str_replace(&quot;&#39;&quot;,&quot;&#39;&#39;&quot;,$_POST[&#39;id&#39;]);
    $_POST[&#39;id&#39;] = substr($_POST[&#39;id&#39;],0,15);
    $result = mysqli_fetch_array(mysqli_query($db,&quot;select 1 from member where length(id)&lt;14 and id=&#39;{$_POST[&#39;id&#39;]}&quot;));
    if($result[0] == 1){
      solve(39);
    }
  }
?&gt;</code></pre><p>최종 목표는 <code>$result[0]</code>의 값을 1로 만드는 것입니다. <code>select 1</code>구문이 존재하므로 쿼리문 조건에 맞는 데이터가 존재할 경우 1을 반환합니다. 따라서 목표는 조건에 맞는 데이터가 존재하도록 쿼리문을 작성하는 것이 됩니다.</p>
<p>코드를 살펴보면 <code>id</code>를 POST 전송으로 전달을 받은 후, <code>\</code>는 공백으로 처리하고 <code>&#39;</code>는 <code>&#39;&#39;</code>으로 변환한 뒤 앞에서부터 15자가 되도록 자릅니다.</p>
<p><code>&#39;</code>가 <code>&#39;&#39;</code>으로 변환이 되기 때문에 <code>id=&#39;</code> 이후 <code>&#39;</code>을 통해 입력 문자열을 닫고 or 구문을 사용하는 것이 불가능해 보입니다. 쿼리문이 정상적으로 동작하기 위해서는 <code>&#39;</code> 하나가 반드시 필요하기 때문에 substr을 통해 <code>&#39;&#39;</code> 중 뒤에 있는 따옴표를 제거할 수 있어 보입니다.</p>
<p>id가 실제 존재하는 id여야 1을 반환하기 때문에 가장 있을만한 admin이라는 id를 사용하겠습니다.  <img src="https://velog.velcdn.com/images/jinwoo_kim/post/9a368961-c3de-40c1-a790-06d43c2b3fbd/image.png" alt="">
substr 길이에 맞춰 입력 값을 사진처럼 넣으면 맨 끝의 <code>&#39;&#39;</code> 중 뒤의 따옴표가 substr에 의해 잘리므로 입력 값과 동일한 값만 남게 됩니다.</p>
<p>이렇게 넣으면 <code>length(id)&lt;14</code>를 만족하지 않는 거 아닌가? 라고 생각할 수도 있지만 length(id)는 id의 길이가 아닌 member 테이블에 저장되어 있는 id 컬럼 값의 길이이므로 조건을 만족합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[webhacking.kr] old-24]]></title>
            <link>https://velog.io/@jinwoo_kim/Webhacking.kr-old-24</link>
            <guid>https://velog.io/@jinwoo_kim/Webhacking.kr-old-24</guid>
            <pubDate>Mon, 30 Mar 2026 02:36:05 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jinwoo_kim/post/34fcfc8b-6685-4161-bc59-a9d20861430d/image.png" alt="">
접속하면 사진과 같이 client ip와 agent가 나오고, 아래에는 <code>Wrong IP!</code>라는 문구가 나옵니다.</p>
<p>view source 링크가 있으므로 코드를 확인해 보겠습니다.</p>
<pre><code>&lt;?php
  extract($_SERVER);
  extract($_COOKIE);
  $ip = $REMOTE_ADDR;
  $agent = $HTTP_USER_AGENT;
  if($REMOTE_ADDR){
    $ip = htmlspecialchars($REMOTE_ADDR);
    $ip = str_replace(&quot;..&quot;,&quot;.&quot;,$ip);
    $ip = str_replace(&quot;12&quot;,&quot;&quot;,$ip);
    $ip = str_replace(&quot;7.&quot;,&quot;&quot;,$ip);
    $ip = str_replace(&quot;0.&quot;,&quot;&quot;,$ip);
  }
  if($HTTP_USER_AGENT){
    $agent=htmlspecialchars($HTTP_USER_AGENT);
  }
  echo &quot;&lt;table border=1&gt;&lt;tr&gt;&lt;td&gt;client ip&lt;/td&gt;&lt;td&gt;{$ip}&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;agent&lt;/td&gt;&lt;td&gt;{$agent}&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&quot;;
  if($ip==&quot;127.0.0.1&quot;){
    solve(24);
    exit();
  }
  else{
    echo &quot;&lt;hr&gt;&lt;center&gt;Wrong IP!&lt;/center&gt;&quot;;
  }
?&gt;</code></pre><p>cookie와 server에서 extract 함수를 통해 변수를 생성합니다.
서버에서 <code>REMOTE_ADDR</code>과 <code>HTTP_USER_AGENT</code> 값을 받아옵니다. 여기서 <code>extract($_COOKIE)</code> 가 뒤에 위치하므로 이름이 <code>REMOTE_ADDR</code>이나 <code>HTTP_USER_AGENT</code> 라는 쿠키를 만들면 값을 변경할 수 있습니다.<img src="https://velog.velcdn.com/images/jinwoo_kim/post/dd1eb5ac-508d-4144-b09f-175bcf6b5d92/image.png" alt="">
사진처럼 쿠키를 통해 값을 변경할 수 있습니다.</p>
<p>문제에서 ip가 <code>127.0.0.1</code>이면 solve를 실행하므로 <code>REMOTE_ADDR</code> 쿠키를 통해 값을 변경하여 해결할 수 있습니다. </p>
<p>하지만 <code>if($REMOTE_ADDR)</code>에서 필터링이 있기 때문에 이를 우회하여 값을 넣어야 합니다.
조건은 <code>..</code>을 <code>.</code>으로 바꾸고, <code>12</code>, <code>7.</code>, <code>0.</code>은 없애버립니다.</p>
<p>우회하는 값은 <code>112277...00...00...1</code> 등이 있습니다.</p>
]]></description>
        </item>
    </channel>
</rss>