์น ์ ํ๋ฆฌ์ผ์ด์
์ ๊ฐ๋ฐํ๊ฑฐ๋ ์ด์ํ ๋ "๋ณด์"์ ๋ ์ฐ์ ์์์์ ๋ฐ๋ฆฌ๊ธฐ ์ฝ๋ค.
๊ธฐ๋ฅ ๊ฐ๋ฐ์ด ๊ธํ๊ณ , ์ผ์ ์ด ์ด๋ฐํ๊ณ , "์ค๋ง ์ฐ๋ฆฌ ์๋น์ค๋ฅผ ๊ณต๊ฒฉํ๊ฒ ์ด?" ํ๋ ์์ผํจ์ด ์๊ธด๋ค.
๊ทธ๋ฐ๋ฐ ์ค์ ์นจํด ์ฌ๊ณ ๋ฅผ ๋ณด๋ฉด ๋๋ถ๋ถ ์ด๋ฏธ ์๋ ค์ง ์ทจ์ฝ์ ์ ํตํด ๋ฐ์ํ๋ค.
OWASP Top 10์ ๊ทธ "์ด๋ฏธ ์๋ ค์ง" ์ทจ์ฝ์ ๋ชฉ๋ก์ด๋ค.
์ด ๊ธ์์๋ 2025๋ ์ต์ ๋ฒ์ ์ ๊ธฐ์ค์ผ๋ก ๊ฐ ํญ๋ชฉ์ ์ฝ๋ ์์ค๊น์ง ํ๊ณ ๋ ๋ค.
OWASP๋ ๋ฌด์์ธ๊ฐ
OWASP(Open Worldwide Application Security Project) ๋ ์น ์ ํ๋ฆฌ์ผ์ด์
๋ณด์ ํฅ์์ ์ํ ๋น์๋ฆฌ ์คํ์์ค ์ปค๋ฎค๋ํฐ๋ค.
2001๋
์ค๋ฆฝ ์ดํ ํ์ค, ๊ฐ์ด๋๋ผ์ธ, ๋๊ตฌ, ๋ฌธ์๋ฅผ ๋ฌด๋ฃ๋ก ๊ณต๊ฐํด์๋ค.
๊ทธ ์ค OWASP Top 10 ์ "์น ์ ํ๋ฆฌ์ผ์ด์
์์ ๊ฐ์ฅ ์ํํ ๋ณด์ ์ทจ์ฝ์ 10๊ฐ์ง"๋ฅผ ์ ๋ฆฌํ ๋ฌธ์๋ก,
์ ์ธ๊ณ ์์ฒ ๊ฐ ์กฐ์ง์ ๋ฐ์ดํฐ๋ฅผ ๋ถ์ํด ์ํ๋ ์์ผ๋ก ๋ญํน์ ๋งค๊ธด๋ค.
OWASP Top 10์ด ์ค์ํ ์ด์ :
- PCI DSS, ISO 27001 ๋ฑ ๊ตญ์ ๋ณด์ ์ธ์ฆ์ ๊ธฐ์ค์ผ๋ก ํ์ฉ
- ๊ฐ๋ฐํ์ ๋ณด์ ์ธ์ ๊ต์ก ๊ต์ฌ๋ก ์ฌ์ฉ
- ์ทจ์ฝ์ ์ ๊ฒ ์ฒดํฌ๋ฆฌ์คํธ ์ญํ
- ๋ฉด์ ์์ ๋ณด์ ์ญ๋์ ๋ฌป๋ ๋จ๊ณจ ์ง๋ฌธ
OWASP Top 10์ ์ญ์ฌ
| ๋ฒ์ | ์ฃผ์ ๋ณํ |
|---|---|
| 2013 | Injection, XSS, CSRF ์ค์ฌ |
| 2017 | XML External Entity (XXE), Insecure Deserialization ์ ์ค |
| 2021 | Insecure Design, Software/Data Integrity ์ ์ค. SSRF ์ฒ์ ๋ฑ์ฅ |
| 2025 | Security Misconfiguration A02๋ก ๊ธ์์น, Supply Chain ์ ์ค, SSRF ์ ๊ฑฐ |
๋ฒ์ ์ด ๋ฐ๋์๋ก "๊ธฐ์ ์ด ๋ฐ์ ํ ๋ฐฉํฅ"์ ๊ทธ๋๋ก ๋ฐ์ํ๋ค.
ํด๋ผ์ฐ๋, CI/CD, ์คํ์์ค ์์กด๋๊ฐ ๋์์ง๋ฉด์ ๊ณต๊ธ๋ง ๊ณต๊ฒฉ์ด ๋ฉ์ธ์ผ๋ก ์ฌ๋ผ์จ ๊ฒ์ด 2025์ ํต์ฌ์ด๋ค.
2021 โ 2025, ๋ฌด์์ด ๋ฌ๋ผ์ก๋
ํต์ฌ ๋ณํ ์์ฝ:
| ๊ตฌ๋ถ | 2021 | 2025 | ๋ณํ ์ด์ |
|---|---|---|---|
| Broken Access Control | A01 | A01 | ๋ณ๋ ์์, 100% ์ฑ์์ ๋ฐ๊ฒฌ |
| Security Misconfiguration | A05 | A02 | ํด๋ผ์ฐ๋/์ปจํ ์ด๋ ํ๊ฒฝ ์ฆ๊ฐ |
| Supply Chain Failures | A06 (์ข์ ๋ฒ์) | A03 | Log4Shell, SolarWinds ์ถฉ๊ฒฉ |
| Cryptographic Failures | A02 | A04 | ์๋์ ํ๋ฝ |
| Injection | A03 | A05 | ์๋์ ํ๋ฝ (ORM ๋ณดํธํ) |
| SSRF | A10 | ์ ๊ฑฐ | ๋ฒ์ฃผ ์ฌํธ |
| Exceptional Conditions | ์์ | A10 | ์๋ก ์ ์ค |
A01:2025 โ Broken Access Control
๊ฐ์
์ ๊ทผ ์ ์ด ์คํจ๋ 2021์ ์ด์ด 2025์์๋ 1์๋ฅผ ์ ์งํ๋ค.
ํ
์คํธ๋ ์ ํ๋ฆฌ์ผ์ด์
์ 100%์์ ๋ฐ๊ฒฌ๋ ๋งํผ ์ฌ์ค์ ๋ชจ๋ ์ฑ์ ์กด์ฌํ๋ค.
์ ๊ทผ ์ ์ด๋ "์ด ์ฌ์ฉ์๊ฐ ์ด ๋ฆฌ์์ค์ ์ ๊ทผํ ์ ์๋๊ฐ"๋ฅผ ํ๋จํ๋ ๋ก์ง์ด๋ค.
์ด๊ฒ ๊นจ์ง๋ฉด ๋ค๋ฅธ ์ฌ๋์ ๋ฐ์ดํฐ๋ฅผ ๋ณด๊ฑฐ๋, ๊ด๋ฆฌ์ ๊ธฐ๋ฅ์ ์ผ๋ฐ ์ฌ์ฉ์๊ฐ ์ธ ์ ์๊ฒ ๋๋ค.
์ทจ์ฝํ ํจํด๋ค
โ IDOR (Insecure Direct Object Reference) โ URL ํ๋ผ๋ฏธํฐ ์กฐ์
// ์ทจ์ฝํ ์ฝ๋: ์ธ์ฆ์ ํ์ง๋ง ์์ ๊ถ ๊ฒ์ฆ ์์
app.get('/api/orders/:orderId', authenticate, async (req, res) => {
const order = await Order.findById(req.params.orderId)
res.json(order) // ๋ค๋ฅธ ์ฌ๋์ ์ฃผ๋ฌธ๋ ์กฐํ ๊ฐ๋ฅ
})
// ์์ ํ ์ฝ๋: ์์ฒญ์ ์์ ์ฌ๋ถ ๊ฒ์ฆ
app.get('/api/orders/:orderId', authenticate, async (req, res) => {
const order = await Order.findOne({
_id: req.params.orderId,
userId: req.user.id // ํ์ฌ ์ฌ์ฉ์ ์์ ์ธ์ง ํ์ธ
})
if (!order) return res.status(403).json({ error: 'Forbidden' })
res.json(order)
})โก ๊ฐ์ ํ์ (Forced Browsing) โ URL ์ง์ ์ ๊ทผ
# ๊ณต๊ฒฉ์๊ฐ ์ง์ URL์ ์ถ์ธกํด์ ์ ๊ทผ ์๋
GET /admin/users
GET /admin/dashboard
GET /internal/reports// ์ทจ์ฝํ ์ฝ๋: ๊ด๋ฆฌ์ ํ์ด์ง URL๋ง ์จ๊น (Security by Obscurity)
app.get('/admin-panel-xk3f', (req, res) => {
// URL ๋ชจ๋ฅด๋ฉด ๋ชป ๋ค์ด์ค๊ฒ ์ง... (ํ๋ฆฐ ์๊ฐ)
res.render('admin')
})
// ์์ ํ ์ฝ๋: ์ญํ ๊ธฐ๋ฐ ์ ๊ทผ ์ ์ด
app.get('/admin', authenticate, requireRole('ADMIN'), (req, res) => {
res.render('admin')
})
function requireRole(role) {
return (req, res, next) => {
if (!req.user.roles.includes(role)) {
return res.status(403).json({ error: 'Insufficient privileges' })
}
next()
}
}โข JWT ํ ํฐ ์กฐ์
// ์ทจ์ฝํ ์ฝ๋: alg=none ๊ณต๊ฒฉ์ ์ทจ์ฝ
const decoded = jwt.verify(token, secret, { algorithms: ['HS256', 'none'] })
// ์์ ํ ์ฝ๋: ํ์ฉ ์๊ณ ๋ฆฌ์ฆ ๋ช
์์ ์ ํ
const decoded = jwt.verify(token, secret, { algorithms: ['HS256'] })๋ฐฉ์ด ์ ๋ต
- Deny by default: ๋ช ์์ ์ผ๋ก ํ์ฉ๋ ๊ฒฝ์ฐ์๋ง ์ ๊ทผ ํ์ฉ
- ์๋ฒ ์ฌ์ด๋์์๋ง ์ ๊ทผ ์ ์ด ํ๋จ (ํด๋ผ์ด์ธํธ hidden ํ๋ ๋ฏฟ์ง ์๊ธฐ)
- ๋ฆฌ์์ค CRUD ์ ์์ ๊ถ ๊ฒ์ฆ ํ์
- ์งง์ ์ ํจ๊ธฐ๊ฐ์ JWT + ๋ง๋ฃ ์ฒ๋ฆฌ
- API ์๋ํฌ์ธํธ Rate Limiting ์ ์ฉ
A02:2025 โ Security Misconfiguration โฌ๏ธ
๊ฐ์
2021๋
A05์์ A02๋ก 3๋จ๊ณ ๊ธ์์นํ๋ค.
ํ
์คํธ๋ ์ฑ์ 100%, ์ ์ฒด ๋ฐ์ ๊ฑด์ 71๋ง 9์ฒ ๊ฑด ์ด์์ผ๋ก ์ฌ์ค์ ๊ฐ์ฅ ํํ ์ทจ์ฝ์ ์ด๋ค.
ํด๋ผ์ฐ๋, ์ปจํ
์ด๋, ๋ง์ดํฌ๋ก์๋น์ค ํ๊ฒฝ์ด ๋ณดํธํ๋๋ฉด์ ์ค์ ํฌ์ธํธ ์๊ฐ ํญ๋ฐ์ ์ผ๋ก ๋์๋ค.
์ค์ ํ๋ ์๋ชป๋๋ฉด ์ ์ฒด ์ธํ๋ผ๊ฐ ๋
ธ์ถ๋๋ค.
๋ํ์ ์ธ ์๋ชป๋ ์ค์
โ ๊ธฐ๋ณธ ์๊ฒฉ ์ฆ๋ช ๋ฏธ๋ณ๊ฒฝ
# ์ทจ์ฝํ ์์: ์ ํ ๊ธฐ๋ณธ๊ฐ ๊ทธ๋๋ก ์ฌ์ฉ
database:
host: localhost
username: admin
password: admin # ๋๋ password, 123456
# MongoDB, Redis, Elasticsearch ๋ฑ์ ๊ธฐ๋ณธ ์ค์ ์ด ์ธ์ฆ ์์
# โ ํฌํธ๋ง ์ด๋ ค์์ผ๋ฉด ๋๊ตฌ๋ ์ ๊ทผ ๊ฐ๋ฅโก ๋ถํ์ํ ํฌํธ/์๋น์ค ์คํ
# ๊ฐ๋ฐ ์ค์ ์ด์ด๋ ๋๋ฒ๊ทธ ํฌํธ๊ฐ ํ๋ก๋์
์๋ ์ด๋ฆผ
# Node.js ๋๋ฒ๊ทธ ํฌํธ
--inspect=0.0.0.0:9229 # ์ธ๋ถ์์ ์ ๊ทผ ๊ฐ๋ฅํ๋ฉด ์๊ฒฉ ์ฝ๋ ์คํ ๊ฐ๋ฅ
# ์ ์ฌ์ ์ผ๋ก ์ํํ ํฌํธ๋ค
22 # SSH (๊ธฐ๋ณธ ํฌํธ)
3306 # MySQL
5432 # PostgreSQL
6379 # Redis
27017 # MongoDBโข ์์ธํ ์๋ฌ ๋ฉ์์ง ๋ ธ์ถ
// ์ทจ์ฝํ ์ฝ๋: ์คํ ํธ๋ ์ด์ค ๊ทธ๋๋ก ๋
ธ์ถ
app.use((err, req, res, next) => {
res.status(500).json({
error: err.message,
stack: err.stack, // DB ๊ตฌ์กฐ, ํ์ผ ๊ฒฝ๋ก, ๋ด๋ถ ๋ก์ง ๋
ธ์ถ
query: err.query // SQL ์ฟผ๋ฆฌ๊น์ง ๋
ธ์ถ
})
})
// ์์ ํ ์ฝ๋: ์ผ๋ฐํ๋ ์๋ฌ ๋ฉ์์ง
app.use((err, req, res, next) => {
logger.error(err) // ๋ด๋ถ ๋ก๊ทธ์๋ง ๊ธฐ๋ก
res.status(500).json({ error: 'Internal server error' })
})โฃ HTTP ๋ณด์ ํค๋ ๋๋ฝ
# ์ทจ์ฝํ ์ค์ : ๋ณด์ ํค๋ ์์
# ์์ ํ ์ค์
add_header X-Content-Type-Options "nosniff";
add_header X-Frame-Options "DENY";
add_header X-XSS-Protection "1; mode=block";
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains";
add_header Content-Security-Policy "default-src 'self'";
add_header Referrer-Policy "strict-origin-when-cross-origin";
add_header Permissions-Policy "geolocation=(), microphone=()";๋ฐฉ์ด ์ ๋ต
- IaC(Infrastructure as Code) ๋ก ํ๊ฒฝ ์ค์ ์ฝ๋ํ + ์ฝ๋ ๋ฆฌ๋ทฐ
- ์ต์ํ์ ์ค์น ์์น: ์ฌ์ฉํ์ง ์๋ ๊ธฐ๋ฅ/์๋น์ค/ํฌํธ๋ ์ ๊ฑฐ
- ๊ฐ๋ฐ/์คํ ์ด์ง/ํ๋ก๋์ ์ค์ ์์ ๋ถ๋ฆฌ
- ์ ๊ธฐ์ ์ธ ์ค์ ๊ฐ์ฌ (CIS Benchmark, CSPM ๋๊ตฌ ํ์ฉ)
- ์๋ฌ ํธ๋ค๋ฌ์์ ์คํ ํธ๋ ์ด์ค ํ๋ก๋์ ๋ ธ์ถ ๊ธ์ง
A03:2025 โ Software Supply Chain Failures ๐
๊ฐ์
2021๋
"์ทจ์ฝํ๊ฑฐ๋ ์ค๋๋ ์ปดํฌ๋ํธ(A06)"์์ ๋ฒ์๊ฐ ํฌ๊ฒ ํ๋๋ ์ ์ค ์นดํ
๊ณ ๋ฆฌ๋ค.
๋จ์ํ "ํจ์น ์ ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ"๋ฅผ ๋์ด์, ์ํํธ์จ์ด๋ฅผ ๋ง๋ค๊ณ ๋ฐฐํฌํ๋ ์ ์ฒด ํ์ดํ๋ผ์ธ์ด ๊ณต๊ฒฉ ๋์์ด ๋๋ค.
์ปค๋ฎค๋ํฐ ์ค๋ฌธ์์ 1์ ์ํ์ผ๋ก 50% ๋ํ, ํ๊ท ๋ฐ์๋ฅ 5.19% ๋ก ๊ฐ์ฅ ๋๋ค.
์ค์ ์ฌ๋ก
SolarWinds (2020):
Orion ์ํํธ์จ์ด์ ๋น๋ ์์คํ
์ ์
์ฑ ์ฝ๋๊ฐ ์ฃผ์
๋จ.
18,000๊ฐ ์ด์ ์กฐ์ง์ด ์ ์ ์
๋ฐ์ดํธ ํ์ผ์ ๋ฐ์ ์ค์น โ ๋ฐฑ๋์ด ์ค์น.
๋ฏธ๊ตญ ์ ๋ถ ๊ธฐ๊ด ๋ค์ ํฌํจ.
Log4Shell (CVE-2021-44228):
์๋ฐ ๋ก๊น
๋ผ์ด๋ธ๋ฌ๋ฆฌ Log4j์ ์ทจ์ฝ์ .
์ ์ธ๊ณ ์๋ฐฑ๋ง ๊ฐ ์๋ฒ๊ฐ ๋จ ํ ์ค์ ๋ก๊ทธ ์
๋ ฅ์ผ๋ก ์๊ฒฉ ์ฝ๋ ์คํ์ ๋
ธ์ถ.
npm Shai-Hulud ์ (2025):
์๊ธฐ ๋ณต์ ์
์ฑ npm ํจํค์ง. 500๊ฐ ์ด์ ํจํค์ง ๋ฒ์ ๊ฐ์ผ, ๊ฐ๋ฐ์ ํ ํฐ์ ํ์ทจํด ์ถ๊ฐ ํ์ฐ.
์ทจ์ฝํ ํจํด
// package.json โ ๋ฒ์ ๊ณ ์ ์์ด ์ต์ ๋ฒ์ ์๋ ์ค์น
{
"dependencies": {
"express": "*", // ์ํ: ์
์์ ์
๋ฐ์ดํธ ์๋ ์ค์น
"lodash": "^4.0.0", // ์ฃผ์: ๋ง์ด๋ ๋ฒ์ ์๋ ์
๊ทธ๋ ์ด๋
"axios": "~1.0.0" // ํจ์น ๋ฒ์ ๋ง ์๋ ์
๊ทธ๋ ์ด๋
}
}# ์์กด์ฑ ์ทจ์ฝ์ ํ์ธ
npm audit
pip-audit
trivy image my-app:latest
# SBOM(Software Bill of Materials) ์์ฑ
syft my-app:latest -o spdx-json > sbom.json๋ฐฉ์ด ์ ๋ต
- SBOM ๊ด๋ฆฌ: ๋ชจ๋ ์์กด์ฑ ๋ชฉ๋กํ, ๋ฒ์ ์ถ์
- ์์กด์ฑ ๋ฒ์ ๊ณ ์ :
package-lock.json,requirements.txt์ ํํ ๋ฒ์ ๋ช ์ - ์ ๊ธฐ ์ทจ์ฝ์ ์ค์บ: Dependabot, Snyk, Trivy
- CI/CD ํ์ดํ๋ผ์ธ ๋ณด์: ๋น๋ ํ๊ฒฝ ๊ฒฉ๋ฆฌ, ์๋ช ๊ฒ์ฆ, MFA ์ ์ฉ
- ์ ๋ขฐํ ์ ์๋ ๋ ์ง์คํธ๋ฆฌ๋ง ์ฌ์ฉ: ์ฌ๋ด ๋ฏธ๋ฌ ๋ ์ง์คํธ๋ฆฌ ์ด์
- ๋จ๊ณ์ ๋ฐฐํฌ(Canary): ์ทจ์ฝํ ์ ๋ฐ์ดํธ ํผํด ๋ฒ์ ์ต์ํ
A04:2025 โ Cryptographic Failures
๊ฐ์
2021๋
A02์์ A04๋ก ํ๋ฝํ์ง๋ง ์ฌ์ ํ ์ฌ๊ฐํ๋ค.
"์ํธํ ์คํจ"๋ ๋จ์ํ "์ํธํ ์ ํจ"๋ฟ ์๋๋ผ ์๋ชป๋ ์ํธํ ์ฌ์ฉ๋ ํฌํจํ๋ค.
์์ฃผ ๋ฐ์ํ๋ ์ค์
โ ๋น๋ฐ๋ฒํธ ํ๋ฌธ ์ ์ฅ ๋๋ ์ฝํ ํด์
import hashlib
import bcrypt
# ์ทจ์ฝํ ์ฝ๋: MD5, SHA1 ์ฌ์ฉ (๋ ์ธ๋ณด์ฐ ํ
์ด๋ธ ๊ณต๊ฒฉ์ ์ทจ์ฝ)
def bad_hash_password(password):
return hashlib.md5(password.encode()).hexdigest()
def also_bad(password):
return hashlib.sha1(password.encode()).hexdigest()
# ์์ ํ ์ฝ๋: bcrypt (์ํธ ์๋ ํฌํจ, ๋๋ฆฐ ํด์)
def good_hash_password(password):
return bcrypt.hashpw(password.encode(), bcrypt.gensalt(rounds=12))
def verify_password(password, hashed):
return bcrypt.checkpw(password.encode(), hashed)โก HTTP๋ก ๋ฏผ๊ฐ ๋ฐ์ดํฐ ์ ์ก
# ์ทจ์ฝ: ๋ก๊ทธ์ธ ํผ์ด HTTP๋ก ์ ์ก
POST http://example.com/login
Content-Type: application/x-www-form-urlencoded
username=admin&password=secret123 # ํ๋ฌธ์ผ๋ก ๋คํธ์ํฌ์ ๋
ธ์ถ
# ์์ : ๋ฐ๋์ HTTPS
POST https://example.com/loginโข ์ทจ์ฝํ TLS ์ค์
# ์ทจ์ฝํ ์ค์ : ๊ตฌ๋ฒ์ TLS ํ์ฉ
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # TLS 1.0, 1.1์ POODLE, BEAST ๊ณต๊ฒฉ์ ์ทจ์ฝ
# ์์ ํ ์ค์
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:ECDHE-RSA-AES128-GCM-SHA256';
ssl_prefer_server_ciphers off;โฃ ํ๋์ฝ๋ฉ๋ ์ํธํ ํค
// ์ทจ์ฝํ ์ฝ๋: ํค๊ฐ ์์ค ์ฝ๋์ ํ๋์ฝ๋ฉ
private static final String SECRET_KEY = "my-super-secret-key-123";
// ์์ ํ ์ฝ๋: ํ๊ฒฝ๋ณ์ ๋๋ Secrets Manager์์ ๋ก๋
private static final String SECRET_KEY = System.getenv("JWT_SECRET");๋ฐฉ์ด ์ ๋ต
| ์ํฉ | ๊ถ์ฅ ์๊ณ ๋ฆฌ์ฆ |
|---|---|
| ๋น๋ฐ๋ฒํธ ์ ์ฅ | bcrypt, Argon2, scrypt |
| ๋์นญ ์ํธํ | AES-256-GCM |
| ๋น๋์นญ ์ํธํ | RSA-2048+, ECDSA |
| ํด์ (์ผ๋ฐ) | SHA-256, SHA-3 |
| TLS | 1.2 ์ด์, 1.3 ๊ถ์ฅ |
| ์ฌ์ฉ ๊ธ์ง | MD5, SHA1, DES, ECB ๋ชจ๋ |
- ๋ฏผ๊ฐ ๋ฐ์ดํฐ๋ ํ์ํ ๊ธฐ๊ฐ๋ง ์ ์ฅ (๋ถํ์ํ ๋ฐ์ดํฐ ์ฆ์ ์ญ์ )
- ํค๋ ์ฝ๋๊ฐ ์๋ Vault, AWS Secrets Manager, ํ๊ฒฝ๋ณ์๋ก ๊ด๋ฆฌ
A05:2025 โ Injection
๊ฐ์
2021๋
A03์์ A05๋ก ํ๋ฝํ๋ค. ORM ์ฌ์ฉ์ด ๋ณดํธํ๋๋ฉด์ ์ ํต์ ์ธ SQL Injection์ด ์ค์๊ธฐ ๋๋ฌธ์ด๋ค.
ํ์ง๋ง SQL ์ธ์๋ ๋ค์ํ Injection ๊ณต๊ฒฉ์ด ์กด์ฌํ๋ฉฐ ์ฌ์ ํ ์ํํ๋ค.
SQL Injection
// ์ทจ์ฝํ ์ฝ๋: ์ฌ์ฉ์ ์
๋ ฅ์ ์ฟผ๋ฆฌ์ ์ง์ concatenation
String query = "SELECT * FROM users WHERE name = '" + username + "'";
// ๊ณต๊ฒฉ์ ์
๋ ฅ: admin' OR '1'='1
// ์คํ๋๋ ์ฟผ๋ฆฌ: SELECT * FROM users WHERE name = 'admin' OR '1'='1'
// โ ๋ชจ๋ ์ฌ์ฉ์ ๋ฐ์ดํฐ ๋ฐํ
// ์์ ํ ์ฝ๋: PreparedStatement ์ฌ์ฉ
String query = "SELECT * FROM users WHERE name = ?";
PreparedStatement stmt = conn.prepareStatement(query);
stmt.setString(1, username);Command Injection
import subprocess
import shlex
# ์ทจ์ฝํ ์ฝ๋: ์ฌ์ฉ์ ์
๋ ฅ์ ์
ธ ๋ช
๋ น์ ์ง์ ์ฝ์
def get_file_info(filename):
output = subprocess.run(f"ls -la {filename}", shell=True, capture_output=True)
return output.stdout
# ๊ณต๊ฒฉ์ ์
๋ ฅ: "file.txt; cat /etc/passwd"
# ์คํ: ls -la file.txt; cat /etc/passwd โ ์์คํ
ํ์ผ ๋
ธ์ถ
# ์์ ํ ์ฝ๋: ๋ฆฌ์คํธ ํํ๋ก ์ ๋ฌ, shell=False
def get_file_info(filename):
output = subprocess.run(["ls", "-la", filename], shell=False, capture_output=True)
return output.stdoutSSTI (Server-Side Template Injection)
from flask import Flask, render_template_string
app = Flask(__name__)
# ์ทจ์ฝํ ์ฝ๋: ์ฌ์ฉ์ ์
๋ ฅ์ ํ
ํ๋ฆฟ์ผ๋ก ์ง์ ๋ ๋๋ง
@app.route('/greet')
def greet():
name = request.args.get('name')
return render_template_string(f"Hello {name}!")
# ๊ณต๊ฒฉ์ ์
๋ ฅ: {{7*7}} โ "Hello 49!" ์ถ๋ ฅ (์ฝ๋ ์คํ ํ์ธ)
# ์ฌํ ๊ณต๊ฒฉ: {{config.items()}} โ ์๋ฒ ์ค์ ๋
ธ์ถ
# ์์ ํ ์ฝ๋: ๋ณ์ ๋ถ๋ฆฌ
@app.route('/greet')
def greet():
name = request.args.get('name')
return render_template_string("Hello {{ name }}!", name=name)๋ฐฉ์ด ์ ๋ต
- Parameterized Query / PreparedStatement ์ฌ์ฉ (SQL)
- ์ฌ์ฉ์ ์ ๋ ฅ์ ํ์ดํธ๋ฆฌ์คํธ ๋ฐฉ์์ผ๋ก ๊ฒ์ฆ
- ORM ์ฌ์ฉ ์์๋ Raw Query ์ฌ์ฉ ๊ธ์ง
- ์ ํ๋ฆฌ์ผ์ด์ ๊ณ์ ์ DB ๊ถํ ์ต์ํ (SELECT๋ง ํ์ํ๋ฉด SELECT๋ง)
- WAF(Web Application Firewall) ์ถ๊ฐ ๋ ์ด์ด๋ก ํ์ฉ
A06:2025 โ Insecure Design
๊ฐ์
2021๋
์ ์ ์ค๋ ํญ๋ชฉ์ผ๋ก A04์์ A06์ผ๋ก ํ๋ฝํ๋ค.
์ด ํญ๋ชฉ์ ์ฝ๋๊ฐ ์๋ ์ค๊ณ ๋จ๊ณ์ ๋ฌธ์ ๋ฅผ ๋ค๋ฃฌ๋ค.
๊ตฌํ์ ์๋ฒฝํ๊ฒ ํด๋, ์ค๊ณ ์์ฒด๊ฐ ์๋ชป๋์ผ๋ฉด ๋ณด์์ด ๊นจ์ง๋ค.
๋ํ ์ฌ๋ก
๋น๋ฐ๋ฒํธ ์ฌ์ค์ โ ์ทจ์ฝํ ํ๋ก์ฐ ์ค๊ณ
๋ฌธ์ ์ :
- ๋ณด์ ์ง๋ฌธ์ ์์ ์์ง๋์ด๋ง์ผ๋ก ์ฝ๊ฒ ํ์ทจ ๊ฐ๋ฅ
- ๋น๋ฐ๋ฒํธ ์ง์ ๋ฐ์ก = ํ๋ฌธ ์ ์ฅ ์ฆ๊ฑฐ
- ์ด๋ฉ์ผ ๊ณ์ ํ์ทจ ์ ์ฆ์ ๊ณ์ ํ์ทจ
์์ ํ ์ค๊ณ:
๋ฐฉ์ด ์ ๋ต
- ๊ฐ๋ฐ ์ Threat Modeling ์ํ (STRIDE, PASTA ๋ฐฉ๋ฒ๋ก )
- Evil User Story ์์ฑ: "๊ณต๊ฒฉ์๋ผ๋ฉด ์ด๋ป๊ฒ ์ ์ฉํ ๊น?"
- ๋ ์ดํธ ๋ฆฌ๋ฐ, ๊ณ์ ์ ๊ธ ์ ์ฑ ์ ์ค๊ณ ๋จ๊ณ์์ ํฌํจ
- Secure Design Patterns ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ฐธ์กฐ (OWASP Proactive Controls)
A07:2025 โ Authentication Failures
๊ฐ์
์ธ์ฆ ์คํจ. 2021๋
"์๋ณ ๋ฐ ์ธ์ฆ ์คํจ(A07)"์์ "์ธ์ฆ ์คํจ(A07)"๋ก ์ด๋ฆ์ด ์ ๋ฆฌ๋๋ค.
๋ก๊ทธ์ธ ๋ฉ์ปค๋์ฆ ์์ฒด์ ๊ฒฐํจ์ ๋ค๋ฃฌ๋ค.
์์ฃผ ๋ฐ์ํ๋ ํจํด
โ Credential Stuffing ๋ฐฉ์ด ์์
// ์ทจ์ฝํ ์ฝ๋: Rate Limit ์์
app.post('/login', async (req, res) => {
const { username, password } = req.body
const user = await User.findOne({ username })
if (user && await bcrypt.compare(password, user.password)) {
res.json({ token: generateToken(user) })
} else {
res.status(401).json({ error: 'Invalid credentials' })
}
})
// ์์ ํ ์ฝ๋: Rate Limit + ๊ณ์ ์ ๊ธ
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15๋ถ
max: 5, // 5ํ ์ด๊ณผ ์ ์ฐจ๋จ
message: 'Too many login attempts'
})
app.post('/login', loginLimiter, async (req, res) => {
// ... ๋์ผ ๋ก์ง
})โก ์ธ์ ID ์ฌ์ฌ์ฉ (Session Fixation)
// ์ทจ์ฝํ ์ฝ๋: ๋ก๊ทธ์ธ ํ ์ธ์
ID ๊ฐฑ์ ์์
app.post('/login', (req, res) => {
req.session.userId = user.id // ๊ธฐ์กด ์ธ์
ID ๊ทธ๋๋ก ์ฌ์ฉ
})
// ์์ ํ ์ฝ๋: ๋ก๊ทธ์ธ ์ฑ๊ณต ์ ์ธ์
์ฌ์์ฑ
app.post('/login', (req, res) => {
req.session.regenerate((err) => { // ์ ์ธ์
ID ๋ฐ๊ธ
req.session.userId = user.id
res.json({ success: true })
})
})โข ๋น๋ฐ๋ฒํธ ์ ์ฑ ์์
// ์ทจ์ฝํ ์ฝ๋: ๋น๋ฐ๋ฒํธ ๋ณต์ก๋ ๊ฒ์ฆ ์์
app.post('/register', (req, res) => {
const { password } = req.body
// "1234", "aaaa" ๊ฐ์ ๋น๋ฐ๋ฒํธ๋ ํ์ฉ
// ์์ ํ ์ฝ๋: ๋ณต์ก๋ ๊ฒ์ฆ
const passwordRegex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/
if (!passwordRegex.test(password)) {
return res.status(400).json({ error: '๋น๋ฐ๋ฒํธ๋ 8์ ์ด์, ๋์๋ฌธ์, ์ซ์, ํน์๋ฌธ์ ํฌํจ ํ์' })
}
})๋ฐฉ์ด ์ ๋ต
- MFA(๋ค์ค ์ธ์ฆ) ํ์ ์ ์ฉ (ํนํ ๊ด๋ฆฌ์ ๊ณ์ )
- ๋ก๊ทธ์ธ ์คํจ ์ ๋์ผํ ์๋ฌ ๋ฉ์์ง ("์์ด๋ ๋๋ ๋น๋ฐ๋ฒํธ๊ฐ ํ๋ ธ์ต๋๋ค")
- ์ธ์
์ฟ ํค:
HttpOnly,Secure,SameSite=Strict - ๋น๋ฐ๋ฒํธ ๋ณต์ก๋ ์ ์ฑ + HIBP(Have I Been Pwned) API๋ก ์ ์ถ ๋น๋ฐ๋ฒํธ ๊ฒ์ฌ
- ์ฅ์๊ฐ ๋ฏธ์ฌ์ฉ ์ธ์ ์๋ ๋ง๋ฃ
A08:2025 โ Software or Data Integrity Failures
๊ฐ์
์ํํธ์จ์ด๋ ๋ฐ์ดํฐ์ ๋ฌด๊ฒฐ์ฑ ๊ฒ์ฆ ์์ด ์ฌ์ฉํ ๋ ๋ฐ์ํ๋ค.
2021๊ณผ ๋์ผํ๊ฒ ์ ์ง๋๋ค.
๋ํ ์ฌ๋ก
โ CI/CD ํ์ดํ๋ผ์ธ ์ทจ์ฝ์
# ์ทจ์ฝํ GitHub Actions: ์ธ๋ถ ์ก์
๋ฒ์ ๊ณ ์ ์์
steps:
- uses: actions/checkout@main # ์ํ: main ๋ธ๋์น๋ ์ธ์ ๋ ๋ณ๊ฒฝ ๊ฐ๋ฅ
# ์์ ํ ์ค์ : ์ปค๋ฐ ํด์๋ก ๊ณ ์
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2โก ๋ฌด๊ฒฐ์ฑ ๊ฒ์ฆ ์๋ ํ์ผ ๋ค์ด๋ก๋
# ์ทจ์ฝํ ๋ฐฉ๋ฒ: ๊ฒ์ฆ ์์ด ์คํ
curl https://example.com/install.sh | bash
# ์์ ํ ๋ฐฉ๋ฒ: ํด์ ๊ฒ์ฆ ํ ์คํ
curl -O https://example.com/install.sh
curl -O https://example.com/install.sh.sha256
sha256sum -c install.sh.sha256 # ํด์ ์ผ์น ํ์ธ ํ
bash install.shโข ์์ ํ์ง ์์ ์ญ์ง๋ ฌํ
// ์ทจ์ฝํ ์ฝ๋: ์ ๋ขฐํ ์ ์๋ ์์ค์์ ์ญ์ง๋ ฌํ
ObjectInputStream ois = new ObjectInputStream(request.getInputStream());
Object obj = ois.readObject(); // ์
์์ ์ง๋ ฌํ ๋ฐ์ดํฐ ์ฒ๋ฆฌ ์ RCE ๊ฐ๋ฅ
// ์์ ํ ์ฝ๋: ์ญ์ง๋ ฌํ ์ ํ์
๊ฒ์ฆ ๋๋ JSON ์ฌ์ฉ
// Java Deserialization Filter (Java 9+)
ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(
"com.example.SafeClass;!*"
);
ois.setObjectInputFilter(filter);๋ฐฉ์ด ์ ๋ต
- ์ธ๋ถ ์ก์ /ํ๋ฌ๊ทธ์ธ์ ์ปค๋ฐ ํด์๋ก ๋ฒ์ ๊ณ ์
- ๋ค์ด๋ก๋ ํ์ผ์ ์ฒดํฌ์ฌ(SHA-256) ๊ฒ์ฆ
- ์ญ์ง๋ ฌํ ์ ํ์ ํ์ดํธ๋ฆฌ์คํธ ์ ์ฉ
- ์ฝ๋ ์๋ช (Code Signing) ์ผ๋ก ๋ฐฐํฌ๋ฌผ ๋ฌด๊ฒฐ์ฑ ๋ณด์ฅ
- CI/CD ํ์ดํ๋ผ์ธ์ ๋น๋ฐ ๊ฐ ์ค์บ ์ถ๊ฐ (gitleaks, truffleHog)
A09:2025 โ Security Logging and Alerting Failures
๊ฐ์
๋ก๊น
๊ณผ ๋ชจ๋ํฐ๋ง์ ์คํจ. 2021๊ณผ ๋์ผํ๊ฒ ์ ์ง๋๋ค.
์ด ํญ๋ชฉ์ ๊ณต๊ฒฉ ์์ฒด๋ฅผ ๋ง๋ ๊ฒ ์๋๋ผ, ๊ณต๊ฒฉ์ ํ์งํ๊ณ ๋์ํ๋ ์ญ๋์ ๊ดํ ๊ฒ์ด๋ค.
ํ๊ท ์นจํด ์ฌ๊ณ ํ์ง๊น์ง 200์ผ ์ด์ ๊ฑธ๋ฆฐ๋ค๋ ํต๊ณ๊ฐ ์๋ค.
๋ก๊ทธ๊ฐ ์์ผ๋ฉด ๋ซ๋ ธ๋์ง์กฐ์ฐจ ๋ชจ๋ฅธ๋ค.
๋ก๊ทธ์ ๋ฐ๋์ ๊ธฐ๋กํด์ผ ํ ๊ฒ๋ค
// ๋ณด์ ์ด๋ฒคํธ ๋ก๊น
์์
const securityLog = {
// ์ธ์ฆ ๊ด๋ จ
'LOGIN_SUCCESS': { level: 'INFO', fields: ['userId', 'ip', 'userAgent', 'timestamp'] },
'LOGIN_FAILURE': { level: 'WARN', fields: ['username', 'ip', 'reason', 'timestamp'] },
'LOGOUT': { level: 'INFO', fields: ['userId', 'ip', 'timestamp'] },
'MFA_FAILURE': { level: 'WARN', fields: ['userId', 'ip', 'timestamp'] },
// ์ ๊ทผ ์ ์ด ๊ด๋ จ
'ACCESS_DENIED': { level: 'WARN', fields: ['userId', 'resource', 'ip', 'timestamp'] },
'PRIVILEGE_ESCALATION_ATTEMPT': { level: 'ERROR', fields: ['userId', 'ip', 'timestamp'] },
// ๋ฐ์ดํฐ ๊ด๋ จ
'SENSITIVE_DATA_ACCESS': { level: 'INFO', fields: ['userId', 'dataType', 'timestamp'] },
'BULK_DATA_EXPORT': { level: 'WARN', fields: ['userId', 'recordCount', 'timestamp'] },
}๋ก๊ทธ์ ์ ๋ ๊ธฐ๋กํ๋ฉด ์ ๋๋ ๊ฒ๋ค
// โ ์ ๋ ๋ก๊ทธ์ ๋จ๊ธฐ๋ฉด ์ ๋๋ ๋ฐ์ดํฐ
logger.info(`Login attempt: username=${username}, password=${password}`) // ๋น๋ฐ๋ฒํธ
logger.info(`Card payment: card=${cardNumber}, cvv=${cvv}`) // ๊ฒฐ์ ์ ๋ณด
logger.info(`Token: ${authToken}`) // ์ธ์ฆ ํ ํฐ
logger.info(`SSN: ${socialSecurityNumber}`) // ๊ฐ์ธ์ ๋ณด
// โ
์์ ํ ๋ก๊น
logger.info(`Login attempt: userId=${userId}, ip=${ip}`)
logger.info(`Payment processed: orderId=${orderId}, amount=${amount}`)๋ฐฉ์ด ์ ๋ต
- ์ค์ํ๋ ๋ก๊ทธ ์์ง (ELK Stack, Splunk, CloudWatch)
- ๋ก๊ทธ ๋ณ์กฐ ๋ฐฉ์ง: append-only ์คํ ๋ฆฌ์ง, ์ธ๋ถ SIEM ์ ์ก
- ์ด์ ํ์ ์๋ฆผ: ๋์ผ IP์์ ์งง์ ์๊ฐ ๋ด ๋ค์ ๋ก๊ทธ์ธ ์คํจ
- ๋ก๊ทธ ๋ณด๊ด ๊ธฐ๊ฐ: ์ต์ 1๋ (GDPR/๊ฐ์ธ์ ๋ณด๋ณดํธ๋ฒ ๊ณ ๋ ค)
- ๋ก๊ทธ ๋ ๋ฒจ ๊ตฌ๋ถ: DEBUG๋ ๊ฐ๋ฐ ํ๊ฒฝ์๋ง, ํ๋ก๋์ ์ INFO ์ด์
A10:2025 โ Mishandling of Exceptional Conditions ๐
๊ฐ์
2025๋
์ ์ค ์นดํ
๊ณ ๋ฆฌ. 2021์ SSRF๊ฐ ์ ๊ฑฐ๋๊ณ ์๋กญ๊ฒ ๋ฑ์ฅํ๋ค.
์ ํ๋ฆฌ์ผ์ด์
์ด ์์ธ ์ํฉ(์ค๋ฅ, ์ฃ์ง ์ผ์ด์ค, ๋ฆฌ์์ค ๋ถ์กฑ) ์ ์ ๋๋ก ์ฒ๋ฆฌํ์ง ๋ชปํ ๋ ๋ฐ์ํ๋ ๋ฌธ์ ๋ฅผ ๋ค๋ฃฌ๋ค.
24๊ฐ์ CWE๊ฐ ๋งคํ๋์ด ์๋ค.
๋ํ ์ฌ๋ก
โ ์์ธ ๋ฐ์ ์ ๋ฆฌ์์ค ๋ฏธํด์
// ์ทจ์ฝํ ์ฝ๋: ์์ธ ๋ฐ์ ์ ํ์ผ ๋ฝ ๋ฏธํด์
public void processUpload(MultipartFile file) throws IOException {
FileOutputStream fos = new FileOutputStream("temp_" + file.getOriginalFilename());
fos.write(file.getBytes()); // ์ฌ๊ธฐ์ ์์ธ ๋ฐ์ ์ fos๊ฐ ๋ซํ์ง ์์
// โ ํ์ผ ์ ๊ธ ์ํ ์ ์ง, ๋์คํฌ ๊ณ ๊ฐ ๊ณต๊ฒฉ ๊ฐ๋ฅ
}
// ์์ ํ ์ฝ๋: try-with-resources
public void processUpload(MultipartFile file) throws IOException {
try (FileOutputStream fos = new FileOutputStream("temp_" + file.getOriginalFilename())) {
fos.write(file.getBytes());
} // ์์ธ ๋ฐ์ํด๋ ์๋์ผ๋ก close() ํธ์ถ
}โก ๊ธ์ต ํธ๋์ญ์ ๋ถ๋ถ ๋กค๋ฐฑ ์คํจ
// ์ทจ์ฝํ ์ฝ๋: ์ด์ฒด ์ค ์ค๋ฅ ์ ๋ถ๋ถ๋ง ์ฒ๋ฆฌ
public void transfer(Long fromId, Long toId, BigDecimal amount) {
accountRepository.deduct(fromId, amount); // ์ถ๊ธ ์ฑ๊ณต
// ์ฌ๊ธฐ์ ์์ธ ๋ฐ์ โ
accountRepository.deposit(toId, amount); // ์
๊ธ ๋๋ฝ โ ๋์ด ์ฌ๋ผ์ง
}
// ์์ ํ ์ฝ๋: ํธ๋์ญ์
์ผ๋ก ๋ฌถ๊ธฐ
@Transactional
public void transfer(Long fromId, Long toId, BigDecimal amount) {
accountRepository.deduct(fromId, amount);
accountRepository.deposit(toId, amount);
// ์์ธ ๋ฐ์ ์ ์ ์ฒด ๋กค๋ฐฑ โ ๋ฐ์ดํฐ ์ผ๊ด์ฑ ์ ์ง
}โข ์๋ฌ ๋ฉ์์ง๋ก ์์คํ ์ ๋ณด ๋ ธ์ถ
# ์ทจ์ฝํ ์ฝ๋
try:
result = db.execute(query)
except Exception as e:
return {"error": str(e)}
# ๋ฐํ ์์: "psycopg2.errors.UndefinedTable: relation 'users' does not exist"
# โ DB ์ข
๋ฅ, ํ
์ด๋ธ ๊ตฌ์กฐ, ๋ฒ์ ์ ๋ณด ๋
ธ์ถ
# ์์ ํ ์ฝ๋
try:
result = db.execute(query)
except Exception as e:
logger.error(f"DB error: {e}", exc_info=True) # ๋ด๋ถ ๋ก๊ทธ์๋ง
return {"error": "์ฒ๋ฆฌ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค. ์ ์ ํ ๋ค์ ์๋ํด์ฃผ์ธ์."}๋ฐฉ์ด ์ ๋ต
- Fail Closed ์์น: ์ค๋ฅ ๋ฐ์ ์ ์์ ํ ์ํ๋ก ์ข ๋ฃ (๋ถ๋ถ ์ฒ๋ฆฌ ๊ธ์ง)
- ๋ฐ์ดํฐ๋ฒ ์ด์ค ์์ ์ ํธ๋์ญ์ ์ผ๋ก ๋ฌถ๊ธฐ
- ๋ฆฌ์์ค๋ ๋ฐ๋์ finally ๋๋ try-with-resources ๋ก ํด์
- Rate Limiting + ๋ฆฌ์์ค ์ฟผํฐ๋ก ๋ฆฌ์์ค ๊ณ ๊ฐ ๊ณต๊ฒฉ ๋ฐฉ์ด
- ์ค์ํ๋ ์์ธ ํธ๋ค๋ฌ ๊ตฌํ (์๋ฌ๋ณ ์ฒ๋ฆฌ ์ผ๊ด์ฑ)
์ ์ฒด ์ํ๋ ์์ฝ
| ๋ฑ๊ธ | ํญ๋ชฉ | ์ด๋ฆ | 2021 ๋๋น | ํต์ฌ ์ํ |
|---|---|---|---|---|
| ๐ด Critical | A01 | Broken Access Control | ์ ์ง | ๋ค๋ฅธ ์ฌ์ฉ์ ๋ฐ์ดํฐ ์ ๊ทผ |
| ๐ด Critical | A02 | Security Misconfiguration | โฌ๏ธ A05โA02 | ์ค์ ์ค๋ฅ๋ก ์์คํ ๋ ธ์ถ |
| ๐ด Critical | A03 | Supply Chain Failures | ๐ ๋ฒ์ ํ๋ | ๋น๋/๋ฐฐํฌ ํ์ดํ๋ผ์ธ ์นจํด |
| ๐ High | A04 | Cryptographic Failures | โฌ๏ธ A02โA04 | ๋ฐ์ดํฐ ํ๋ฌธ ๋ ธ์ถ |
| ๐ High | A05 | Injection | โฌ๏ธ A03โA05 | ์ ์์ ์ฟผ๋ฆฌ/๋ช ๋ น ์คํ |
| ๐ High | A06 | Insecure Design | โฌ๏ธ A04โA06 | ์ํคํ ์ฒ ์์ค ์ทจ์ฝ์ |
| ๐ก Medium | A07 | Auth Failures | ์ ์ง | ๊ณ์ ํ์ทจ |
| ๐ก Medium | A08 | Integrity Failures | ์ ์ง | ์์กฐ ์ฝ๋/๋ฐ์ดํฐ ์คํ |
| ๐ก Medium | A09 | Logging Failures | ์ ์ง | ์นจํด ํ์ง ๋ถ๊ฐ |
| ๐ก Medium | A10 | Exceptional Conditions | ๐ SSRF ๋์ฒด | ์์ธ ์ฒ๋ฆฌ ์คํจ |
์ค๋ฌด ๋ณด์ ์ฒดํฌ๋ฆฌ์คํธ
๊ฐ๋ฐ ์๋ฃ ์ ์๋ ํญ๋ชฉ์ ์ ๊ฒํด๋ณด์.
์ ๊ทผ ์ ์ด
- ๋ชจ๋ API ์๋ํฌ์ธํธ์ ์ธ์ฆ/์ธ๊ฐ ์ ์ฉ ํ์ธ
- IDOR ๊ฐ๋ฅ์ฑ โ URL์ ID ํ๋ผ๋ฏธํฐ ์์ ๊ถ ๊ฒ์ฆ
- ๊ด๋ฆฌ์ ๊ธฐ๋ฅ ์ผ๋ฐ ์ฌ์ฉ์ ์ ๊ทผ ๋ถ๊ฐ ํ์ธ
- JWT ์๊ณ ๋ฆฌ์ฆ ๋ช ์์ ๊ณ ์ , ๋ง๋ฃ ์ฒ๋ฆฌ
์ค์ ๋ณด์
- ๊ธฐ๋ณธ ๊ณ์ /๋น๋ฐ๋ฒํธ ๋ณ๊ฒฝ
- ๋ถํ์ํ ํฌํธ/์๋น์ค ๋นํ์ฑํ
- HTTP ๋ณด์ ํค๋ ์ค์ (securityheaders.com ์์ ์ ๊ฒ ๊ฐ๋ฅ)
- ์๋ฌ ๋ฉ์์ง ์ฌ์ฉ์ ๋ ธ์ถ ์ฌ๋ถ ํ์ธ
- ํ๋ก๋์ ์์ ๋๋ฒ๊ทธ ๋ชจ๋ ๋นํ์ฑํ
๊ณต๊ธ๋ง ๋ณด์
- ์์กด์ฑ ๋ฒ์ ๊ณ ์ (lock ํ์ผ ์ปค๋ฐ)
-
npm audit/pip-audit/trivy์ ๊ธฐ ์คํ - CI/CD ์ธ๋ถ ์ก์ ์ปค๋ฐ ํด์ ๊ณ ์
- SBOM ์์ฑ ๋ฐ ๊ด๋ฆฌ
์ํธํ
- ๋น๋ฐ๋ฒํธ bcrypt/Argon2๋ก ์ ์ฅ
- ์ ์ก ๊ตฌ๊ฐ HTTPS ๊ฐ์ (HSTS ํค๋)
- ์ํธํ ํค ์ฝ๋์์ ๋ถ๋ฆฌ (ํ๊ฒฝ๋ณ์/Secrets Manager)
- TLS 1.2 ์ด์๋ง ํ์ฉ
๋ก๊น
- ๋ก๊ทธ์ธ ์ฑ๊ณต/์คํจ ๊ธฐ๋ก
- ์ ๊ทผ ๊ฑฐ๋ถ ์ด๋ฒคํธ ๊ธฐ๋ก
- ๋ก๊ทธ์ ๋น๋ฐ๋ฒํธ/ํ ํฐ/์นด๋๋ฒํธ ํฌํจ ์ฌ๋ถ ํ์ธ
- ์ด์ ํ์ ์๋ฆผ ์ค์
๋ง์น๋ฉฐ
OWASP Top 10์ ์ต์ํ์ ๊ธฐ์ค์ด๋ค.
์ด 10๊ฐ์ง๋ฅผ ๋ชจ๋ ์ก์๋ ๋ณด์์ด ์๋ฒฝํ๋ค๋ ์๋ฏธ๋ ์๋๋ค.
ํ์ง๋ง ์ด๊ฒ์กฐ์ฐจ ๋์น๊ณ ์๋ค๋ฉด, ๊ณต๊ฒฉ์ ์
์ฅ์์ ๊ทธ๋ฅ ๊ณต๊ฐ๋ ๋ฌธ์ ์ฌ๋ ๊ฒ๊ณผ ๊ฐ๋ค.
๋ณด์์ ํ ๋ฒ ์ ์ฉํ๊ณ ๋๋๋ ๊ฒ ์๋๋ค.
์ํ ํ๊ฒฝ์ ๊ณ์ ๋ณํ๊ณ , OWASP๋ 4๋
๋ง๋ค ๋ฐ๋๋ค.
์ฝ๋ ๋ฆฌ๋ทฐ ๋ ์ฒดํฌ๋ฆฌ์คํธ ํ ์ค์ฉ ์ถ๊ฐํ๋ ๊ฒ๋ถํฐ ์์ํด๋ณด์.
๊ด๋ จ ๊ธ
- OSI 7๊ณ์ธต๊ณผ ๋ณด์ ์ํ โ ๋คํธ์ํฌ ๊ณ์ธต๋ณ ๊ณต๊ฒฉ ์ดํด
- Spring Boot ์ธ์ฆ ๊ตฌํ ๊ฐ์ด๋ โ JWT, OAuth2, Spring Security
- Docker ์์ํ๊ธฐ โ ์ปจํ ์ด๋ ํ๊ฒฝ๊ณผ ๋ณด์ ์ค์
