내 웹사이트를 내가 해킹해봤다 (OWASP Top 10과 보안의 기본)
1. "비밀번호 몰라도 들어갈 수 있어"
고등학교 컴퓨터 동아리 시간이었습니다. 친구가 PHP로 공들여 만든 로그인 페이지를 자랑하더군요.
"야, 이거 비밀번호 20자리로 설정했고, 특수문자도 넣었어. 절대 못 뚫어."
저는 웃으며 아이디 입력창에 이렇게 쳤습니다.
admin' --
비밀번호 칸은 비워둔 채 엔터를 쳤습니다.
결과는? "관리자님 환영합니다"
친구의 얼굴이 하얗게 질렸던 그 순간을 잊을 수 없습니다. 친구는 "어떻게 비밀번호도 없이 들어왔어?"라며 패닉에 빠졌습니다.
이것이 제 인생 첫 해킹이자, SQL Injection과의 첫 만남이었습니다.
그날 저는 깨달았습니다. 보안은 "강력한 자물쇠(비밀번호)"를 다는 게 아니라, "문틀(코드)" 자체가 튼튼해야 한다는 것을요.
2. OWASP Top 10: 해커들의 명예의 전당
웹 보안은 방대하지만, 다행히 전 세계 보안 전문가들이 "가장 위험한 웹 취약점 10개"를 모아놓은 리스트가 있습니다.
바로 OWASP (Open Web Application Security Project) Top 10입니다.
이 리스트는 몇 년마다 갱신되는데, 해커들이 가장 애용하는 공격 루트이자 개발자들이 가장 실수하기 쉬운 부분들을 모아놓은 것입니다.
이 10가지만 확실히 이해하고 막아도, 여러분 사이트 보안의 90%는 해결됩니다.
오늘날 가장 흔하게 발견되는 핵심 취약점 4가지를 깊이 있게 파헤쳐 봤습니다.
3. Top 4 취약점과 방어법
1위 - Broken Access Control (비정상적인 접근 제어)
옛날엔 Injection이 1위였지만, 지금은 이게 1위입니다. 가장 흔하고 막기 어렵기 때문입니다.
흔히 IDOR (Insecure Direct Object References)라고도 불립니다.
- 해커의 생각:
"내 주문 내역 URL이
/orders/100이네? 그럼 /orders/101로 바꾸면 다른 사람 주문도 보일까?"
-
취약한 코드:
// DB에서 그냥 ID로 조회해서 바로 줌 (권한 검사 없음)
app.get('/orders/:id', (req, res) => {
const order = db.findOrder(req.params.id);
res.json(order);
});
- 방어법:
모든 요청마다 "이 사람이 이 정보를 볼 권한(Ownership)이 있는가?"를 확인해야 합니다.
app.get('/orders/:id', (req, res) => {
const order = db.findOrder(req.params.id);
// 핵심: 주문자가 현재 로그인한 유저와 같은지 확인
if (order.userId !== req.session.userId) {
return res.status(403).send("Forbidden");
}
res.json(order);
});
주의: 프론트엔드에서 버튼을 hidden으로 숨기는 건 보안이 아닙니다. API 레벨에서 막아야 합니다.
2위 - Cryptographic Failures (암호화 실패)
민감한 뎅터를 평문(Plain Text)으로 저장하거나, 약한 암호화 알고리즘을 쓰는 경우입니다.
- 상황: 데이터베이스가 해킹당했습니다.
users 테이블을 열어보니 비밀번호가 password123 그대로 보입니다. 해커는 룰루랄라 이 비밀번호로 다른 사이트들(네이버, 구글)도 로그인해 봅니다. (사람들은 비밀번호를 재사용하니까요)
-
실수:
- 비밀번호를
base64로 인코딩함 (인코딩은 암호화가 아닙니다! 누구나 풀 수 있어요.)
MD5, SHA-1 같은 낡은 해시 함수 사용.
-
방어법:
- 비밀번호는 절대 저장하지 말고, 해시(Hash)값만 저장하세요.
- Bcrypt, Argon2 같은 강력한 "Salted Hash" 알고리즘을 쓰세요. (Rainbow Table 공격 방지)
- HTTPS(TLS)를 강제하여 네트워크 구간을 암호화하세요.
3위 - Injection (인젝션)
제 친구를 울렸던 바로 그 녀석입니다. 사용자의 입력을 코드의 일부로 실행해버리는 취약점입니다.
-
SQL Injection 원리:
SELECT * FROM users WHERE id = '$inputId';
여기에 $inputId로 ' OR '1'='1을 넣으면?
SELECT * FROM users WHERE id = '' OR '1'='1';
1=1은 항상 참(True)이므로, DB의 모든 사용자 정보를 뱉어냅니다.
- 방어법:
사용자의 입력은 무조건 "데이터"로만 취급해야 합니다.
PreparedStatement (매개변수화된 쿼리)를 쓰면 안전합니다.
// Good: 입력값을 ? 로 치환해서 받음 (컴파일러가 코드로 인식 안 함)
db.query('SELECT * FROM users WHERE id = ?', [inputId]);
4위 - Security Misconfiguration (보안 설정 오류)
코드는 완벽한데 설정 때문에 뚫리는 경우입니다.
-
상황:
- 기본 비밀번호(
admin / password)를 안 바꿈.
- 에러가 났는데 화면에 상세한 스택 트레이스(Stack Trace)를 다 보여줌. (DB 구조, 파일 경로, 프레임워크 버전 등이 노출됨)
- S3 버킷을
Public으로 열어둠.
-
방어법:
- 프로덕션 환경에서는 디버그 모드를 끄세요.
- 기본 계정과 포트 설정을 변경하세요.
- 불필요한 기능과 포트는 닫으세요 (Principle of Least Privilege).
4. 해커처럼 생각하기 (Hacker's Mindset)
보안을 잘하려면 방어자가 아니라 공격자의 입장에서 생각해야 합니다. 이를 "Offensive Security"라고 합니다.
- 입력값 검증: "이 입력창(나이)에 숫자가 아니라 스크립트(
<script>)를 넣으면 실행될까?" (XSS)
- 경계값 테스트: "송금 금액에
-10000원을 넣으면 내 통장에 돈이 들어올까?" (Business Logic Error)
- 세션 탈취: "로그인 쿠키를 복사해서 시크릿 모드 브라우저에 넣으면 로그인이 될까?" (Session Hijacking)
개발자는 기능을 "동작하게" 만드는 사람이지만,
보안 전문가는 기능을 "고장 나게" 만드는 사람입니다.
이 두 가지 시각을 모두 가져야 비로소 '시니어 개발자'라고 할 수 있습니다.
5. 마무리 - "설마"가 사람 잡는다
"에이, 누가 우리 같은 작은 사이트를 해킹하겠어? 가져갈 정보도 없는데."
가장 위험한 생각입니다.
해커들은 일일이 손으로 타겟을 정하고 해킹하지 않습니다.
자동화된 봇(Bot)이 24시간 내내 전 세계의 IP를 스캔하며 admin.php, wp-login.php, .env 파일을 찾고 다닙니다.
취약점이 발견되면 그 즉시 뚫리는 겁니다.
구멍이 있으면, 뚫리는 건 if가 아니라 when의 문제입니다.
지금 당장 여러분의 코드를 돌아보세요. admin' -- 한 줄에 무너지는 성은 아닌지.
I Hacked My First Website (OWASP Top 10 Deep Dive)
1. "I Don't Need Your Password"
It was a high school computer club meeting. My friend was proudly showing off a login page he built with PHP.
"Hey, look at this. I set the password to 20 characters with special symbols. You'll never crack it."
I smiled, walked up to his keyboard, and typed this into the ID field:
admin' --
I left the password field empty and hit Enter.
The result? "Welcome, Admin."
I'll never forget the look of absolute horror on his face. "How... how did you get in without the password?"
This was my first hack, and my first encounter with SQL Injection.
That day, I learned a valuable lesson: Security isn't about putting a strong lock (User Password) on the door. It's about making sure the doorframe (Code) itself doesn't fall off.
2. OWASP Top 10: The Hall of Fame for Hackers
Web security is vast, but thankfully, security experts worldwide have compiled a list of the "10 Most Dangerous Web Vulnerabilities."
It's called the OWASP (Open Web Application Security Project) Top 10.
This list is updated every few years. It represents the vulnerabilities that hackers love the most and developers implement poorly the most often.
If you understand and mitigate just these 10, you solve 90% of your site's security issues.
Let's dive deep into the top 4 vulnerabilities that are most relevant today.
3. Top 4 Vulnerabilities & Defenses
#1: Broken Access Control
Formerly lower on the list, this is now #1. Why? Because it's common and hard to automate detection for.
Often called IDOR (Insecure Direct Object References).
- Hacker's Thought:
"My order URL is
/orders/100. If I change it to /orders/101, can I see someone else's order?"
-
Vulnerable Code:
// Vulnerable: Fetches order by ID without checking who owns it
app.get('/orders/:id', (req, res) => {
const order = db.findOrder(req.params.id);
res.json(order);
});
- Defense:
The server must verify "Does this user have permission (Ownership) to see this specific data?" for EVERY request.
// Secure: Checks ownership
app.get('/orders/:id', (req, res) => {
const order = db.findOrder(req.params.id);
if (order.userId !== req.session.userId) {
return res.status(403).send("Forbidden");
}
res.json(order);
});
Note: Hiding delete buttons on the frontend with CSS is NOT security. Hackers use Postman, not Chrome.
#2: Cryptographic Failures
Storing sensitive data in plain text or using weak encryption.
- Scenario: Your database gets breached (dumped). If passwords are stored as
password123, the game is over. Hackers will try those email/password combos on other sites (Credential Stuffing).
-
Mistakes:
- Encoding passwords with
Base64. (Encoding is NOT encryption. It's reversible.)
- Using obsolete hash functions like
MD5 or SHA-1.
-
Defense:
- NEVER store passwords in plain text.
- Use strong, Salted Hash algorithms like Bcrypt or Argon2. These make Rainbow Table attacks impossible.
- Enforce HTTPS (TLS) everywhere to encrypt data in transit.
#3: Injection
The classic vulnerability that made my friend cry. It occurs when untrusted user input is interpreted as code.
#4: Security Misconfiguration
The code is perfect, but the setup is flawed.
-
Scenario:
- Leaving default credentials active (
admin / admin).
- Verbose Error Messages: Showing full Stack Traces in production. This reveals your framework version, file paths, and database logic to the attacker.
- Leaving Cloud Storage (S3 Buckets) open to the public.
-
Defense:
- Disable debug mode in production.
- Change all default ports and passwords.
- Follow the Principle of Least Privilege: Give only the minimum necessary permissions.
4. Think Like a Hacker (Offensive Security)
To be a good defender, you must think like an attacker. This mindset shift is what separates junior devs from seniors.
- Input Validation: "If I put
<script>alert(1)</script> in the username field, will it execute?" (XSS)
- Boundary Testing: "If I transfer
-100 dollars to my friend, will 100 dollars be subtracted from him and added to me?" (Logic Error)
- Session Hijacking: "If I copy my login cookie to another browser, does the session persist?"
Developers are trained to make features limitlessly functional.
Security experts are trained to find where those functions break limitlessly.
You need to wear both hats.
5. FAQ: Common Security Questions
Q. Is my framework (React/Next.js) safe?
Modern frameworks protect you from a lot (like XSS via automatic escaping), but they are not silver bullets. They cannot protect you from Logic Errors (like Broken Access Control) or Misconfigurations. You still need to code securely.
Q. What are automated tools?
You don't have to check everything manually.
- SAST (Static Application Security Testing): Tools like SonarQube or CodeQL analyze your source code for vulnerabilities.
- DAST (Dynamic Application Security Testing): Tools like OWASP ZAP or Burp Suite attack your running application from the outside to find holes.
- SCA (Software Composition Analysis): Tools like
npm audit or Snyk check if your dependencies (node_modules) have known vulnerabilities.
Q. How often should I audit?
Ideally, continuously. Integrate security tools into your CI/CD pipeline (DevSecOps). Security shouldn't be a "once a year" event; it should be part of every commit.
7. Bonus: Security Tools for Developers
You don't need to be a hacker to test your site. Use these free tools:
- OWASP ZAP (Zed Attack Proxy): An open-source web scanner. Just enter your URL, and it will find SQLi, XSS, and broken headers for you.
- Burp Suite (Community Edition): The industry standard for manual penetration testing. Great for intercepting requests and modifying packets.
- SonarQube: A static analysis tool that integrates with GitHub. It spots vulnerable code patterns (like hardcoded passwords) before you even merge the PR.
8. Conclusion: "Surely Not" Kills You
"Who would hack our small startup? We have nothing to steal."
This is the most dangerous assumption.
Hackers don't sit there manually typing URLs. They use Automated Bots that scan millions of IPs 24/7, looking for known vulnerabilities like open .env files or outdated WordPress plugins.
If you have a hole, you will be found. It's not a matter of if, but when.
Secure your code today. Don't let a single quote ' be the reason your startup fails.
7. Bonus: Security Tools for Developers
You don't need to be a hacker to test your site. Use these free tools:
- OWASP ZAP (Zed Attack Proxy): An open-source web scanner. Just enter your URL, and it will find SQLi, XSS, and broken headers for you.
- Burp Suite (Community Edition): The industry standard for manual penetration testing. Great for intercepting requests and modifying packets.
- SonarQube: A static analysis tool that integrates with GitHub. It spots vulnerable code patterns (like hardcoded passwords) before you even merge the PR.