Hands-on Workshop | 3 Hours
DEVDAY EP.3
Learn to find and fix security vulnerabilities
through manual code review and automated scanning tools
| Time | Session |
|---|---|
| 09:00 - 09:45 | Security Fundamentals & OWASP Top 10 |
| 09:45 - 10:15 | Manual Code Review + Exercise |
| 10:15 - 10:25 | Break |
| 10:25 - 10:45 | Security Scanning Tools Overview |
| 10:45 - 11:35 | Hands-on Lab — Security Scanning |
| 11:35 - 11:55 | CI/CD Integration & Best Practices |
| 11:55 - 12:00 | Q&A & Wrap-up |
| Bonus | Appendix: Security Dockerfile & Docker Compose (Self-study) |
เรียนรู้การตรวจสอบความปลอดภัยของ source code ด้วย manual code review
และการใช้เครื่องมือ security scanning tools เพื่อค้นหาช่องโหว่
และความเสี่ยงด้านความปลอดภัย ก่อนนำ code ไป deploy จริง
เมื่อจบ workshop นี้ ผู้เรียนจะสามารถ:
Semgrep, Syft, Gitleaks45 minutes
ยิ่งเจอช่องโหว่เร็ว ค่าใช้จ่ายในการแก้ไขยิ่งต่ำ
| Phase | Cost to Fix |
|---|---|
| Design | 1x |
| Development | 6.5x |
| Testing | 15x |
| Production | 100x |
10 ความเสี่ยงด้านความปลอดภัยที่สำคัญที่สุดสำหรับ Web Applications
A01 Broken Access Control
A02 Security Misconfiguration
A03 Supply Chain Failures
A04 Cryptographic Failures
A05 Injection
A06 Insecure Design
A07 Auth Failures
A08 Integrity Failures
A09 Logging & Alerting
A10 Exceptional Conditions
ผู้ใช้เข้าถึงข้อมูลหรือ function ที่ไม่ได้รับอนุญาต
/api/user/123 เป็น /api/user/456/admin โดยตรง// Bad: ไม่มี authorization check
app.get('/api/user/:id', (req, res) => {
return db.getUser(req.params.id);
});
# Bad: server fetch URL อะไรก็ได้
resp = requests.get(user_provided_url)
# Attacker: http://169.254.169.254 → AWS metadata!
# Good: allowlist + block internal IPs
ALLOWED_DOMAINS = ["api.example.com"]
parsed = urlparse(user_provided_url)
if parsed.hostname not in ALLOWED_DOMAINS:
raise ValueError("URL not allowed")
ตั้งค่าไม่ถูกต้อง ปล่อย default settings เปิด features ไม่จำเป็น
DEBUG=True ใน production — stack trace รั่วadmin/admin# Bad: DEBUG ใน production
app.run(debug=True)
# → stack trace + credentials รั่ว!
# Good: env-based config
app.run(debug=env.get('DEBUG', 'false') == 'true')
ใช้ library CI/CD pipeline หรือ dependency ที่ถูก compromise
# Bad: ไม่ pin version, ไม่ verify
pip install requests
npm install lodash
# Good: pin version + verify integrity
pip install requests==2.31.0 \
--hash=sha256:abc123...
npm ci # uses lockfile
การเข้ารหัสที่ไม่เหมาะสม ไม่มี หรือใช้ algorithm ล้าสมัย
bcrypt / argon2 สำหรับ password# Bad
hash = md5(password)
# Good
hash = bcrypt.hashpw(password, bcrypt.gensalt())
ข้อมูล user ถูกนำไป execute โดยตรงโดยไม่ผ่าน sanitize
# Bad: string concatenation
query = "SELECT * FROM users WHERE id = '" + user_input + "'"
# Attacker: ' OR 1=1 -- → ได้ทั้ง database!
# Good: parameterized query
cursor.execute("SELECT * FROM users WHERE id = %s", (user_input,))
// Bad: render user input directly
document.innerHTML = userInput;
// Attacker: <script>steal(document.cookie)</script>
# Bad: direct OS command
os.system('ping ' + user_input)
# Attacker: ; rm -rf / → ลบทั้ง server!
# Good: use subprocess with list args
subprocess.run(['ping', '-c', '4', validated_host], capture_output=True)
<!-- Bad: DTD enabled by default -->
<!DOCTYPE foo [<!ENTITY xxe SYSTEM "file:///etc/passwd">]>
<data>&xxe;</data>
# Good: disable DTD processing
from defusedxml import ElementTree
tree = ElementTree.parse(untrusted_input)
ปัญหาจากการออกแบบที่ไม่ปลอดภัยตั้งแต่แรก
ไม่สามารถแก้ได้ด้วย code fix อย่างเดียว
ระบบยืนยันตัวตนที่อ่อนแอ
123456, passwordไม่ตรวจสอบความถูกต้องของ software updates หรือ CI/CD pipeline
ไม่มี logging เพียงพอ ตรวจจับ attack ไม่ได้
การจัดการ errors/exceptions ไม่ดี ทำให้เกิดช่องโหว่ร้ายแรง
# Bad: fail-open + expose internals
try:
result = db.query(sql)
except Exception as e:
return {"error": str(e)} # leaks DB schema!
# Good: fail-closed + sanitize
try:
result = db.query(sql)
except DatabaseError:
log.exception("Query failed")
return {"error": "Service unavailable"}
30 minutes
| Category | Check |
|---|---|
| Input Validation | ทุก user input ต้อง validate ทั้ง type, length, format |
| Authentication | Password policy, session timeout, MFA |
| Authorization | ทุก endpoint มี auth check หรือไม่ |
| Session | Secure, httpOnly, sameSite cookies |
| Cryptography | Algorithm ทันสมัย ไม่ hardcode keys |
| Error Handling | ไม่ expose stack trace ให้ user |
| Sensitive Data | ไม่เก็บ credentials ใน source code |
กลุ่มละ 2-3 คน | 10 นาที
หาช่องโหว่ให้มากที่สุดใน sample code
ช่องโหว่ที่ซ่อนไว้:
ใช้ checklist จากหัวข้อก่อนหน้าเป็นแนวทาง
Code: lab/vulnerable-app/
10 minutes | 10:15 - 10:25
20 minutes
Static Application Security Testing
วิเคราะห์ source code โดยไม่ต้อง run
Semgrep
Software Composition Analysis
ตรวจสอบ dependencies ที่มีช่องโหว่
Syft + Dependency-Track
ค้นหา credentials ที่ commit เข้า repo
Gitleaks
Open source, pattern-based code scanning | 30+ ภาษา
# Scan with auto config
semgrep scan --config auto .
# Output JSON
semgrep scan --config auto --json --output results.json .
SBOM Generator
# Generate SBOM
syft . -o cyclonedx-json > sbom.json
# Scan container
syft <image> -o cyclonedx-json
Vulnerability Management
รวมผลจาก tools ทั้งหมด (150+ importers) — deduplicate, track, report
ค้นหา API keys, passwords, tokens ที่หลุดเข้า Git
# Scan repo
gitleaks detect --source . \
--report-format json \
--report-path gitleaks-report.json
# Pre-commit hook (ป้องกันก่อน commit)
gitleaks protect --staged
.gitleaks.toml50 minutes
# Clone workshop repo
git clone https://github.com/Wsangsrichan/DEVDAY-EP-3.git
cd DEVDAY-EP-3/lab/vulnerable-app
# Verify tools
semgrep --version
syft --version
gitleaks version
Lab checklist: docs/lab-checklist.md
15 minutes
# Step 1: Run scan
semgrep scan --config auto --json --output results.json .
# Step 2: View results
cat results.json | python3 -m json.tool | less
10 minutes
# Step 1: Generate SBOM
syft . -o cyclonedx-json > sbom.json
# Step 2: View SBOM contents
cat sbom.json | python3 -m json.tool | head -50
# Step 3: Upload to Dependency-Track (Web UI or API)
curl -X POST "http://localhost:8081/api/v1/bom" \
-H "X-Api-Key: YOUR_API_KEY" \
-F "project=PROJECT_UUID" \
-F "bom=@sbom.json"
10 minutes
# Step 1: Run scan
gitleaks detect --source . \
--report-format json \
--report-path gitleaks-report.json
# Step 2: View results
cat gitleaks-report.json | python3 -m json.tool
10 minutes
เลือก 3-5 critical vulnerabilities แล้วแก้ไข:
# Re-scan to verify fixes
semgrep scan --config auto .
gitleaks detect --source .
Solutions: lab/solutions/
10 minutes
ตรวจสอบ Dockerfile และ docker-compose.yml ใน lab/vulnerable-app/
ช่องโหว่ที่ซ่อนไว้:
Container ≠ Isolation — ขอบเขตที่โดนเปิดเผยโดยการ misconfiguration
COPY . ดึง .git, .env เข้า imagedocker inspectLayer 1 (base): python:3.12-slim-bookworm ← OS + runtime
Layer 2 (deps): pip install -r requirements.txt ← Your dependencies
Layer 3 (app): COPY app.py /app/ ← Your code
Layer 4 (config): CMD ["python", "app.py"] ← Entrypoint
-slim แทน full image ลด attack surface# Scan image for vulnerabilities
docker scout cve python:3.12-slim-bookworm
# Pin by digest (immutable) — ไม่แปลี่ยนถ้าไม่อัปเดตระบุ
FROM python:3.12-slim-bookworm@sha256:abc123...
# Or pin by version + use SBOM
syft python:3.12-slim-bookworm -o cyclonedx-json
# Inside container (root)
# CVE-2024-1086 (netfilter) — container escape exploit
python3 exploit.py
# → ได้ shell บน host machine เลย!
# Attack chain:
# 1. App vulnerability (SQLi, RCE) → code execution in container
# 2. Container root → exploit kernel capability
# 3. Container escape → full access to host
# 4. Host access → pivot ไป containers/processes อื่นใน cluster
# docker-compose.yml
security_opt:
- no-new-privileges:true # ห้าม escalate privileges
- cap-drop: # ทิ้งทุก Linux capabilities
- ALL
- cap-add:
- NET_BIND_SERVICE # เฉพาะที่จำเป็นจริง
# Dockerfile
USER appuser # เรียบร้อย — ไม่มี root เลย
Default container มี ~14 capabilities เปิดอยู่ — ส่วนใหญ่ไม่จำเป็น
# ทุกคนเห็น secrets ถ้ามี access ถึง container
$ docker inspect vulnerable-app
# → environment section: แสดงทุก env var รวม passwords
$ docker compose config
# → แสดงทุก secret ใน compose file อ่านง่าย
# ถ้า push image ไป registry:
# → env vars อยู่ใน image layers (ใคร pull ก็เห็น)
| Method | Pros | Cons |
|---|---|---|
| Docker Secrets | encrypted at rest, not in layers | Swarm only (ไม่มี K8s) |
| K8s Secrets | encrypted, RBAC, versioned | ต้องมี K8s cluster |
| Vault / AWS SM | rotation, audit, centralized | complexity, dependency |
_FILE suffix | ง่าย, ใช้ได้กับ secrets | ยังต้องจัดการ file permissions |
| Tool | Scans | ใช้ใน CI/CD |
|---|---|---|
| Docker Scout | Image CVEs, SBOM, advice | Built-in Docker CLI |
| Trivy | Image, filesystem, IaC, secrets | Free, comprehensive |
| Syft | SBOM generation (CycloneDX/SPDX) | Already in our lab |
| Grype | Vulnerabilities from SBOM | Pairs with Syft |
| Hadolint | Dockerfile best practices | Lint rules |
| Dockle | Image analysis, size, policy | Free |
# Scan image for CVEs
trivy image python:3.12-slim-bookworm
# Scan Dockerfile for best practices
hadolint Dockerfile
# Generate SBOM + scan for vulns
syft . -o cyclonedx-json | grype sbom:
| # | Vulnerability | Risk | Impact |
|---|---|---|---|
| 1 | FROM python:latest — ไม่ pin version | Medium | Image ไม่ reproducible, อาจมี CVE |
| 2 | ไม่มี USER — รันเป็น root | Critical | Container escape → host compromise |
| 3 | COPY . — ไม่มี .dockerignore | Medium | .git, .env, secrets หลุดเข้า image |
| 4 | ไม่มี HEALTHCHECK | High | Dead container ไม่ถูก restart |
| 5 | CMD python app.py — shell form | Low | SIGTERM ไม่ถึง app, zombie processes |
| 6 | ไม่มี multi-stage build | Medium | Build tools + source อยู่ใน final image |
| 7 | ไม่มี resource limits | High | DDoS, crypto mining, fork bomb |
| # | Vulnerability | Risk | Impact |
|---|---|---|---|
| 8 | Secrets เป็น plaintext env vars | Critical | docker inspect, image layers |
| 9 | ports: "5000:5000" — bind all interfaces | High | ทุกคนบน internet เข้าถึงได้ |
| 10 | DB port 5432 exposed to host | High | Brute force DB, extract data |
| 11 | Redis ไม่มี requirepass | Critical | อ่าน/เขียน cache ได้ทุกคน |
| 12 | ไม่มี network segmentation | High | compromised app → pivot to DB |
| 13 | Host volume mounts ./uploads:/app | Medium | Container อ่าน/เขียน host filesystem |
| 14 | ไม่มี no-new-privileges | High | Setuid binary → privilege escalation |
# Pin version + use slim image (reduced attack surface)
FROM python:3.12-slim-bookworm
# Non-root user — container escape ยากขึ้นมาก
RUN groupadd -r appuser && useradd -r -g appuser appuser
# Multi-stage build — final image: app code + runtime only
FROM python:3.12-slim-bookworm AS builder
COPY --from=builder /root/.local /home/appuser/.local
# Healthcheck — Docker รู้ว่า app ตาย แล้ว restart
HEALTHCHECK CMD python -c "..."
# Exec form — PID 1 = app, SIGTERM ทำงาน
CMD ["python", "app.py"]
# Run as non-root (ทำที่สุดท้าย!)
USER appuser
secrets:
db_password:
file: ./secrets/db_password.txt
services:
app:
secrets:
- db_password
environment:
- DB_PASSWORD_FILE=/run/secrets/db_password
services:
app:
ports:
- "127.0.0.1:5000:5000"
security_opt:
- no-new-privileges:true
read_only: true
deploy:
resources:
limits:
cpus: "1.0"
memory: 256M
healthcheck:
test: ["CMD", "python", "-c", "..."]
networks:
backend:
internal: true
20 minutes
| Stage | Tool | Action |
|---|---|---|
| Pre-commit | Gitleaks | Block secrets before commit |
| Pull Request | Semgrep | SAST scan, block on Critical/High |
| Build | Syft | Generate SBOM |
| Post-build | Dependency-Track | Analyze vulnerabilities |
| Weekly | Full scan | Report to team |
name: Security Scan
on: [pull_request]
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Semgrep SAST
uses: semgrep/semgrep-action@v1
with:
config: auto
- name: Gitleaks Secret Scan
uses: gitleaks/gitleaks-action@v2
- name: Generate SBOM
run: |
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s
syft . -o cyclonedx-json > sbom.json
- name: Upload SBOM
uses: actions/upload-artifact@v4
with:
name: sbom
path: sbom.json
| SAST | Semgrep |
| SCA | Syft |
| Vuln Mgmt | Dependency-Track |
| Aggregator | DefectDojo |
| Secrets | Gitleaks |
| Container | Trivy, Syft |
| CI/CD | GitHub Actions |
Resources:
Workshop materials: github.com/Wsangsrichan/DEVDAY-EP-3
Happy Secure Coding
Security Dockerfile & Docker Compose
Self-study / Reference Material
| # | Check Item |
|---|---|
| 1 | ใช้ minimal base image (Alpine/Distroless/slim) |
| 2 | ไม่ run เป็น root (มี USER instruction) |
| 3 | ใช้ multi-stage builds |
| 4 | Pin base image version (SHA256 digest) |
| 5 | มี .dockerignore (ไม่ COPY .git, .env, node_modules) |
| 6 | ไม่มี secrets hardcode ใน Dockerfile/Compose |
| 7 | ใช้ COPY แทน ADD |
| 8 | มี HEALTHCHECK |
| 9 | read_only: true + tmpfs สำหรับ writable dirs |
| 10 | cap_drop: ALL + no-new-privileges |
| 11 | Resource limits (CPU, memory, pids) |
| 12 | Network segmentation (internal networks) |
| 13 | Ports bind เฉพาะ localhost สำหรับ internal services |
| 14 | Log size limits + centralized logging |
| 15 | Scan image ด้วย Trivy/Syft ก่อน deploy |