WebValid
ทีม WebValid

กับดัก Vibe Coding: 6 ช่องโหว่ React ที่ซ่อนอยู่

AI Coding React Next.js ความปลอดภัย Vibe Coding

บทความนี้ครอบคลุมโครงการที่สร้างด้วย React + Next.js App Router หลักการความปลอดภัยเป็นสากล แต่ตัวอย่างโค้ดและไฟล์กำหนดค่า (next.config.js, vercel.json) จะเฉพาะเจาะจงสำหรับสแต็กนี้

คุณขอให้ผู้ช่วย AI เขียนส่วนประกอบให้ มันเขียนให้ มันทำงานได้ การทดสอบผ่านหมด คุณคลิก Merge

และหลังจากนั้น — ข้อมูลรั่วไหล, คะแนน Google PageSpeed เป็นศูนย์, ข้อร้องเรียนจากผู้พิการทางสายตา และคำถามที่ว่า: “ทำไมเราไม่มี security headers?”

เครื่องมือเขียนโค้ด AI ไม่รู้บริบทการใช้งานจริงของคุณ (production) สำหรับพวกเขาไม่มีความแตกต่างระหว่างบทช่วยสอนสำหรับผู้เริ่มต้นกับแอปพลิเคชันทางการเงินที่มีผู้ใช้จริง ผลลัพธ์ที่ได้คือ พวกเขาเขียนโค้ดที่ “ทำงานได้” แต่แอบละเมิดกฎจาก OWASP Top 10 อย่างเงียบๆ

นี่คือ 6 ช่องโหว่ที่ผู้ช่วย AI มักสอดแทรกเข้าไปในโค้ด React + Next.js และวิธีค้นหาพวกมันใน 10 วินาที

Fact-Check: AI สร้างโค้ดที่มีช่องโหว่ได้อย่างไร

🔍 หลักฐาน · ความลำเอียงของข้อมูลฝึกฝน · การไม่เข้าใจบริบท

ตาม การวิเคราะห์พื้นที่เก็บข้อมูลสาธารณะของ GitHub พบว่าเปอร์เซ็นต์ที่สำคัญของการรั่วไหลด้านความปลอดภัยในโครงการ React มาจากรูปแบบทั่วไปที่พบในข้อมูลการฝึกฝน เช่น การใช้ dangerouslySetInnerHTML โดยไม่มีการทำความสะอาด (sanitization) หรือการใส่ค่าตัวแปรสภาพแวดล้อมลงในโค้ดฝั่งหน้าบ้านโดยตรง (hardcoding)

ในการทดสอบภายในของเราที่ WebValid พบว่าผู้ช่วยเขียนโค้ด AI (Cursor, Copilot, ChatGPT) ประสบความสำเร็จในการระบุและแก้ไขช่องโหว่ได้ก็ต่อเมื่อได้รับ รายงานที่มีโครงสร้าง จากเครื่องมือสแกนภายนอกเท่านั้น เมื่อถูกขอให้ “ตรวจสอบโค้ดนี้เพื่อความปลอดภัย” โดยไม่มีบริบทเพิ่มเติม โมเดลพลาดช่องโหว่ทางสถาปัตยกรรม (เช่น ปัญหาการรั่วไหลของ API key ที่อธิบายด้านล่าง) ถึง 68%


การใช้ dangerouslySetInnerHTML โดยไม่ทำความสะอาด

🔴 วิกฤต · การขโมยเซสชันผู้ใช้, ค่าปรับ GDPR · OWASP A03:2021 Injection

ผู้ช่วย AI ชอบรูปแบบนี้มาก เมื่อคุณขอให้ “แสดงผล HTML จาก API” พวกเขามักจะเขียนแบบนี้:

// ❌ โค้ด AI ที่ไม่ดี
function UserBio({ bio }: { bio: string }) {
  return <div dangerouslySetInnerHTML={{ __html: bio }} />;
}

ถ้า bio มาจากผู้ใช้ — ยินดีด้วย คุณโดน XSS แล้ว ผู้โจมตีจะใส่ <script>document.cookie</script> และขโมยเซสชันของผู้ใช้ไป

// ✅ วิธีแก้ไข: ทำความสะอาดผ่าน DOMPurify
import DOMPurify from "dompurify";

function UserBio({ bio }: { bio: string }) {
  const sanitizedBio = DOMPurify.sanitize(bio);
  return <div dangerouslySetInnerHTML={{ __html: sanitizedBio }} />;
}

จากข้อมูล การค้นหาบน GitHub พบการใช้ dangerouslySetInnerHTML โดยไม่ทำความสะอาดในโครงการ React นับพันโครงการ ผู้ช่วย AI ทำซ้ำรูปแบบนี้จากข้อมูลการฝึกฝน โดยไม่ได้ตั้งคำถามว่า “HTML นี้มาจากไหน?”


API key รั่วไหลใน Client Bundle

🔴 วิกฤต · ความสูญเสียทางการเงินโดยตรง · OWASP A02:2021 Cryptographic Failures

บาปที่พบบ่อยที่สุดของ Vibe Coding คือการที่ผู้ช่วย AI มักจะ “แก้ไข” การเรียกใช้ API ฝั่งไคลเอนต์ที่เสีย โดยการเพิ่มคำนำหน้า NEXT_PUBLIC_ ให้กับคีย์ลับของคุณ สิ่งนี้ทำให้การเรียกใช้งานผ่านได้ แต่มันกลับไปฝังคีย์ลับของคุณไว้ใน JavaScript bundle ที่เป็นสาธารณะ ซึ่งใครๆ ก็มองเห็นได้ผ่าน DevTools

// ❌ โค้ด AI ที่ไม่ดี — คีย์รั่วไหลเข้าไปใน JS bundle
"use client";
const response = await fetch("https://api.openai.com/v1/chat/completions", {
  headers: {
    Authorization: `Bearer ${process.env.NEXT_PUBLIC_OPENAI_API_KEY}`, // ❌ รั่วไหล
  },
});

วิธีแก้ไข: ย้ายตรรกะที่ละเอียดอ่อนทั้งหมดไปยัง Server Action หรือ Route Handler ซึ่งตัวแปรสภาพแวดล้อมจะถูกเก็บไว้อย่างปลอดภัยที่ฝั่งเซิร์ฟเวอร์เท่านั้น

สำหรับการเจาะลึกเกี่ยวกับกลไกการรั่วไหลของ bundle และวิธีใช้การป้องกันขั้นสูง เช่น Next.js Taint API โปรดดูคู่มือที่ครอบคลุมของเรา: คู่มือการรั่วไหลของ API Key


ขาด Security Headers (CSP, X-Frame-Options, HSTS)

🟠 สูง · Clickjacking, การแย่งชิงโทเค็นผ่าน MitM · OWASP A05:2021 Security Misconfiguration

ผู้ช่วย AI ไม่ได้กำหนดค่า HTTP headers ให้ ซึ่งเข้าใจได้เพราะพวกเขาเขียนส่วนประกอบ ไม่ใช่การตั้งค่าเซิร์ฟเวอร์ ผลลัพธ์คือไซต์ของคุณอาจถูกฝังใน <iframe> บนโดเมนที่ประสงค์ร้าย ทำให้เกิดการโจมตีแบบ clickjacking ได้

สำหรับ Next.js + Vercel มี สองวิธี ในการเพิ่ม headers:

วิธีแรก — next.config.js (สำหรับการโฮสต์เอง):

// ✅ next.config.js
const securityHeaders = [
  { key: "X-Frame-Options", value: "SAMEORIGIN" },
  { key: "X-Content-Type-Options", value: "nosniff" },
  { key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
  {
    key: "Content-Security-Policy",
    value: "default-src 'self'; script-src 'self';",
  },
];

module.exports = {
  async headers() {
    return [{ source: "/(.*)", headers: securityHeaders }];
  },
};

วิธีที่สอง — vercel.json (สำหรับ Vercel — แนะนำ):

{
  "headers": [
    {
      "source": "/:path*",
      "headers": [
        {
          "key": "Strict-Transport-Security",
          "value": "max-age=63072000; includeSubDomains; preload"
        },
        { "key": "X-Content-Type-Options", "value": "nosniff" },
        { "key": "X-Frame-Options", "value": "SAMEORIGIN" },
        { "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" }
      ]
    }
  ]
}

WebValid จะช่วยตรวจสอบการมีอยู่ของ headers เหล่านี้ทั้งหมดในการตอบสนอง HTTP ได้ในการสแกนครั้งเดียว


console.log รั่วไหลข้อมูลที่ละเอียดอ่อนไปยัง Production

🟡 ปานกลาง · ข้อมูลส่วนบุคคลรั่วไหลไปยังเครื่องมือบันทึกภายนอก · OWASP A09:2021 Security Logging Failures

ระหว่างการทำ Vibe Coding มักจะมีโค้ดสำหรับการแก้ปัญหา (debug logs) หลงเหลืออยู่:

// ❌ โค้ด AI ที่ไม่ดี
async function loginUser(credentials: Credentials) {
  console.log("Login attempt:", credentials); // รหัสผ่านรั่วไหลใน Log!
  const user = await authService.login(credentials);
  console.log("User logged in:", user); // โทเค็นรั่วไหลใน Log!
  return user;
}

หาก Log ในการใช้งานจริง (production) ถูกรวบรวมไว้ในเครื่องมืออย่าง Sentry, Datadog หรือ Logtail — คุณเพิ่งจะ ส่งรหัสผ่านของผู้ใช้ไปยังบริการภายนอก

// ✅ วิธีแก้ไข: บันทึกเฉพาะข้อมูลที่ไม่ละเอียดอ่อน
import { createLogger } from "@your-scope/logger";

const logger = createLogger({ scope: "AuthService" });

async function loginUser(credentials: Credentials) {
  logger.info("Login attempt", { email: credentials.email }); // เฉพาะ email
  const user = await authService.login(credentials);
  logger.info("Login successful", { userId: user.id }); // เฉพาะ ID
  return user;
}

โครงสร้างทางเซมานติก (Semantic) และ ARIA ที่ผิดพลาด

🟡 ปานกลาง · เสียทราฟฟิก SEO, ละเมิดมาตรฐาน ADA · Accessibility Violation (WCAG 2.1)

ผู้ช่วย AI มักจะทำให้ทุกอย่างคลิกได้โดยการใส่ onClick ลงใน <div> ซึ่งมันรวดเร็วและดูเหมือนทำงานได้ แต่จะฆ่าทั้งความสามารถในการไต่เว็บ (crawlability) และการเข้าถึง (accessibility):

// ❌ โค้ด AI ที่ไม่ดี
function ProductCard({ product }: { product: Product }) {
  return (
    <div onClick={() => navigate(`/products/${product.id}`)}>
      <div>{product.name}</div>
      <div>{product.price}</div>
    </div>
  );
}

ปัญหาที่เกิดขึ้น:

// ✅ วิธีแก้ไข: ใช้มาตรฐาน HTML ที่ถูกต้อง
function ProductCard({ product }: { product: Product }) {
  return (
    <article>
      <a href={`/products/${product.id}`}>
        <h2>{product.name}</h2>
        <span>{product.price} $</span>
      </a>
    </article>
  );
}

Server Actions ที่ไม่มีการตรวจสอบสิทธิ์

🔴 วิกฤต · การสั่งงานในนามของผู้ใช้อื่น · OWASP A01:2021 Broken Access Control

ผู้ช่วย AI มักจะสร้าง Server Actions แต่ลืมรวมการตรวจสอบสิทธิ์ (authorization checks) มาให้ด้วย

// ❌ โค้ด AI ที่ไม่ดี
"use server";

export async function deleteAccount(userId: string) {
  await db.user.delete({ where: { id: userId } });
}

ใครๆ ก็สามารถส่ง POST request และลบบัญชีของคนอื่นได้

// ✅ วิธีแก้ไข: ตรวจสอบสิทธิ์ภายใน Action
"use server";

import { getServerSession } from "next-auth";

export async function deleteAccount(userId: string) {
  const session = await getServerSession();

  if (!session || session.user.id !== userId) {
    throw new Error("Unauthorized");
  }

  await db.user.delete({ where: { id: userId } });
}

วิธีค้นหาทั้ง 6 ช่องโหว่ใน 10 วินาที

คุณสามารถตรวจสอบโค้ดด้วยตนเอง หรือใช้เวลาทั้งวันในการตรวจสอบอย่างละเอียด แต่มีวิธีที่เร็วกว่านั้น:

  1. รันโปรเจกต์ของคุณผ่าน ngrok เพื่อให้ได้ URL สาธารณะ
  2. วาง URL นั้นลงใน WebValid
  3. รับ ai-fix-prompt ในรูปแบบ Markdown ที่พร้อมใช้งาน — นำไปวางในผู้ช่วย AI ของคุณและแก้ไขทุกอย่างใน 2 นาที

รายการตรวจสอบความปลอดภัยจาก Generative AI 6 ข้อของคุณ

  1. การจัดการอินพุต: AI ใช้ dangerouslySetInnerHTML หรือไม่?
  2. ตัวแปรสภาพแวดล้อม: มีคีย์ลับหลุดหรือไม่?
  3. ส่วนหัวความปลอดภัย: คุณได้กำหนดค่า vercel.json หรือไม่?
  4. ข้อมูลรั่วไหล: มีการใช้คำสั่ง console.log โดยตรงหรือไม่?
  5. SEO แบบโต้ตอบ: ตรวจสอบองค์ประกอบที่คลิกได้ของคุณ
  6. การตรวจสอบสิทธิ์การกระทำ: ทุกการกระทำ 'use server' เริ่มต้นด้วยการตรวจสอบสิทธิ์หรือไม่?

ทดสอบโปรเจกต์ของคุณได้ฟรีบน WebValid


เอกสารอ้างอิงอย่างเป็นทางการ

Security

Next.js

Vercel

Accessibility

SEO

บทความนี้มีประโยชน์หรือไม่?