arrayref 0.3.10 อยู่บน crates.io แค่ 86 นาที แต่มัลแวร์ทำงานตั้งแต่ตอน build ก่อนที่จะมีใครเปิดโปรแกรม
arrayref 0.3.10 อยู่บน crates.io แค่ 86 นาที แต่มัลแวร์ทำงานตั้งแต่ตอน build โค้ดร้ายไม่ได้อยู่ใน arrayref แต่อยู่ในแพ็กเกจที่ติดตั้งตามมาโดยไม่มีใครเปิดดู

แพ็กเกจขนาดเล็กในภาษา Rust อย่าง arrayref เวอร์ชัน 0.3.10 อยู่บน crates.io ซึ่งเป็นคลังแพ็กเกจกลางของภาษานี้ได้แค่ 86 นาที ก่อนที่ทีมความปลอดภัยจะลบทิ้ง
ตัวแพ็กเกจมีชุดคำสั่งแมโครสั้นๆ แค่สี่ตัว เทียบคลังนี้ได้กับ npm ของ JavaScript หรือ PyPI ของ Python จนเมื่อเช้าวันที่ 20 สิงหาคม 2026 มีคนเข้าใช้บัญชีของเจ้าของแพ็กเกจ แล้วอัปโหลดเวอร์ชันใหม่ที่พ่วงมัลแวร์ขึ้นไป
มีสองอย่างในเหตุการณ์นี้ที่ทำให้ไม่ใช่เรื่องของคนเขียน Rust อย่างเดียว อย่างแรก คนที่โดนไม่มีใครพิมพ์คำสั่งติดตั้ง arrayref เองเลยสักคน แต่มันติดมากับของอย่างอื่นที่ติดตั้ง อย่างที่สองคือโค้ดร้ายทำงานตั้งแต่ตอน build หรือตอนที่เครื่องกำลังประกอบโปรแกรมขึ้นมา โดยไม่ต้องรอให้ใครกดเปิดโปรแกรมเลยสักครั้ง
ถ้าทุกวันนี้เราให้ AI agent ช่วยเขียนโค้ด แล้วพอ agent เสนอชื่อแพ็กเกจมาให้ติดตั้ง พอเราอ่านผ่านๆ แล้วพิมพ์ yes เราก็ยืนอยู่ตรงจุดเดียวกับคนที่โดนพอดี
ของที่เราไม่ได้เลือก แต่ติดมาด้วย

เวลาสั่งติดตั้งแพ็กเกจสักตัว เราไม่ได้ของแค่ตัวนั้นตัวเดียว เพราะแพ็กเกจนั้นมีรายการของตัวเองว่าต้องใช้อะไรต่ออีก และของที่มันเรียกก็มีรายการของมันต่ออีกชั้น ลากกันลงไปเป็นทอดๆ จนของที่ลงเครื่องจริงมีมากกว่าชื่อที่เราพิมพ์ไปมาก แพ็กเกจชั้นล่างที่พ่วงต่อกันมานี้ ในวงการเรียกว่า transitive dependency หรือแพ็กเกจที่เราไม่เคยสั่ง แต่ของที่เราติดตั้งจำเป็นต้องใช้
arrayref ก็อยู่ในจุดนั้นพอดี ไลบรารีสำหรับทำหน้าจอโปรแกรมด้วย Rust ต่างทำงานอยู่บนไลบรารีพื้นฐานอย่าง winit กับ tiny-skia แล้วสองตัวนั้นก็ดึง arrayref ต่อลงไปอีกทอด งานทำหน้าจอด้วย Rust จำนวนมากจึงมี arrayref พ่วงอยู่ข้างล่าง โดยที่โค้ดของเราไม่เคยเอ่ยชื่อมันเลยสักบรรทัด
ตรงนี้พลาดง่าย เพราะสิ่งที่เราตรวจคือรายการที่เราเห็น ซึ่งก็คือของที่เราเลือกเอง ส่วนรายการที่ลงเครื่องจริงยาวกว่านั้นมาก และแทบไม่มีใครเปิดอ่านทีละชื่อ
จุดนี้เองที่เชื่อมกับวิธีทำงานแบบใหม่ของเรา ตอนให้ AI agent ช่วยเขียนโค้ด มันจะเสนอชื่อแพ็กเกจมาเรื่อยๆ แล้วรอเรากดอนุมัติ ซึ่งเป็นจังหวะที่กดผ่านได้ง่ายมาก เคยมีตัวเลขจากผู้พัฒนา AI อย่าง Anthropic ว่าผู้ใช้กดอนุมัติผ่านราว 93% จนผู้พัฒนาเองยังเลิกฝากความปลอดภัยไว้กับสายตาคน คำถามจึงไม่ใช่แค่ว่าเราได้อ่านชื่อที่ agent เสนอหรือไม่ แต่ของที่ตามชื่อนั้นเข้ามาอีกทั้งพวง ใครจะอ่านให้ล่ะ
โค้ดร้ายไม่ได้อยู่ใน arrayref
จุดที่ทำให้เคสนี้แปลกคือ พอเปิดโค้ดของ arrayref 0.3.10 แล้วไม่เจออะไรผิดปกติเลย ไฟล์ src/lib.rs เหมือนเวอร์ชันก่อนหน้าทุกตัวอักษร และไฟล์ตั้งค่าของมันยังระบุชัดเจนว่า build = false แปลว่าตัวมันเองไม่มีสคริปต์อะไรรันตอนประกอบแพ็กเกจ
สิ่งที่เพิ่มเข้ามามีเพียงบรรทัดเดียวในไฟล์ตั้งค่า
[dependencies.proc-macro1]
version = "1.0.107"
บรรทัดนี้หมายความว่าโปรเจกต์นี้ขอใช้แพ็กเกจชื่อ proc-macro1 ซึ่ง Cargo ที่เป็นเครื่องมือติดตั้งและ build ของ Rust จะดึงพร้อมทั้งคอมไพล์ทุกแพ็กเกจที่ประกาศไว้ ไม่ว่าโค้ดจริงจะเรียกใช้หรือไม่ก็ตาม บรรทัดเดียวจึงพอแล้วที่จะพา proc-macro1 เข้ามาอยู่ในเครื่องจนได้รัน
proc-macro1 ไม่ใช่ proc-macro2 ซึ่งเป็นแพ็กเกจตัวจริงที่คนเขียนแมโครใช้กันทั่วโลก มันคือเทคนิคการหลอกชื่ออย่าง typosquat ที่ตั้งชื่อให้เพี้ยนจากของจริงไปนิดเดียวเพื่อให้คนอ่านผ่านตา คนทำก็ไม่ได้ทำลวกๆ ข้างในโฟลเดอร์ src/ คือโค้ดของ proc-macro2 ที่คัดลอกมาทั้งชุดแล้วไล่เปลี่ยนชื่อ แม้แต่ลิงก์ในคอมเมนต์ยังเปลี่ยนตาม ผลคือมันทำงานได้เหมือนของจริง โปรแกรมยัง build ผ่านตามปกติ ไม่มีอะไรพัง และไม่มีอะไรฟ้องเตือน
ในไฟล์ข้อมูลแพ็กเกจ ผู้โจมตียังใส่ชื่อเจ้าของเป็น David Tolnay นักพัฒนา Rust ที่คนในวงการรู้จักในฐานะผู้สร้าง proc-macro2 ตัวจริง พร้อมลิงก์โปรเจกต์ที่เปิดแล้วขึ้น 404 เพราะไม่มีอยู่จริง ส่วนบัญชีที่อัปโหลดขึ้นไปใช้ชื่อ dtolney ซึ่งต่างจากบัญชีจริง dtolnay เพียงตัวอักษรเดียว
build script รันก่อนที่โปรแกรมจะประกอบเสร็จด้วยซ้ำ
build script คือโปรแกรมเล็กๆ ที่แพ็กเกจแนบมาด้วย แล้วเครื่องจะรันให้อัตโนมัติตอนกำลังประกอบแพ็กเกจนั้น หน้าที่ปกติของมันคืองานจุกจิก เช่นตรวจว่าเครื่องนี้เป็นระบบอะไร หรือสร้างไฟล์บางอย่างเตรียมไว้ก่อน ที่ต้องรู้คือมันรันด้วยสิทธิ์เท่ากับเรา บนเครื่องเรา ตั้งแต่ก่อนโปรแกรมจะประกอบเสร็จ
build script ของ proc-macro1 เริ่มจากซ่อนที่อยู่ของเซิร์ฟเวอร์ปลายทาง โดยหั่นเป็นชิ้นและเข้ารหัสไว้ แล้วค่อยประกอบกลับตอนรัน คนที่กวาดหาข้อความน่าสงสัยในซอร์สโค้ดจึงหาไม่เจอ จากนั้นมันจะตรวจว่าเครื่องใช้ระบบอะไร โดยรองรับสี่แบบคือ Linux, Windows และ macOS (ทั้งเครื่องชิป Intel และ Apple) แล้วดาวน์โหลดไฟล์โปรแกรมที่ตรงกับระบบนั้นลงมา
การดาวน์โหลดใช้การเชื่อมต่อแบบเข้ารหัสก็จริง แต่คนเขียนตั้งค่าตัวตรวจใบรับรองให้ผ่านทุกใบที่เจอ พูดง่ายๆ คือล็อกประตูไว้แต่ไม่มีใครตรวจว่าคนที่ถือกุญแจเป็นใคร พอโหลดเสร็จ บน Linux กับ macOS มันจะเขียนไฟล์ลง /tmp/rust-setup กำหนดสิทธิ์ให้รันได้ แล้วปล่อยให้ทำงานแยกออกไป ส่วนบน Windows จะเขียนสคริปต์ PowerShell กับตัวเรียกอีกชั้นไว้ในโฟลเดอร์ชั่วคราว แล้วสั่งรันแบบไม่ขึ้นหน้าต่าง คอมเมนต์ในโค้ดระบุไว้เองว่าที่ต้องอ้อมแบบนั้นเพราะจะได้หลุดจากการควบคุมของ Cargo กระบวนการนี้จึงยังทำงานต่อได้แม้ build จะจบไปแล้ว
การติดตั้งจึงเท่ากับการรันโค้ดของคนอื่น ไม่ใช่แค่ดาวน์โหลดไฟล์มาวางเฉยๆ ประโยคที่ว่า "ยังไม่ได้เปิดโปรแกรมเลย ไม่น่าเป็นอะไร" ใช้ไม่ได้กับเหตุการณ์แบบนี้ เพราะงานของมัลแวร์จบไปตั้งแต่ตอน build แล้ว
และนี่ไม่ใช่ปัญหาเฉพาะของ Rust เพราะฝั่ง npm ก็มีสคริปต์ที่รันอัตโนมัติตอนติดตั้ง ส่วนฝั่ง Python ก็มี setup.py ที่รันตอนติดตั้งเหมือนกัน เป็นช่องทางเดียวกัน ต่างกันแค่ชื่อเรียก
คำเตือนให้อัปเดต กลายเป็นช่องทางพาเข้ากับดัก

ส่วนที่พลิกความคาดหมายอยู่ตรงวิธีที่มันแพร่กระจาย
บัญชีที่โดนยึดไปไม่ได้แค่ปล่อยเวอร์ชันใหม่แล้วนั่งรอเฉยๆ แต่ใช้คำสั่ง yank เพื่อประกาศถอนเวอร์ชันเก่าที่ปลอดภัยทั้งหมดตั้งแต่ 0.3.5 ถึง 0.3.9 ด้วย แม้ระบบจะยังเปิดให้ดาวน์โหลดไฟล์เดิมได้สำหรับโปรเจกต์เก่าที่ล็อกเวอร์ชันไว้ ซึ่งตามปกติแล้ว yank เป็นกลไกด้านความปลอดภัยที่ใช้ตอนเจอบั๊กร้ายแรงและต้องการเตือนให้ทุกคนอัปเดตไปใช้เวอร์ชันใหม่
พอถูก yank ตัว Cargo จะแจ้งเตือนว่า consider updating to a version that is not yanked แปลว่าแนะนำให้อัปเดตไปเวอร์ชันที่ยังไม่โดนถอน และตอนนั้นเวอร์ชันที่ยังไม่โดนถอนก็เหลืออยู่ตัวเดียว นั่นคือ 0.3.10 ที่มีมัลแวร์แฝงอยู่
คนที่ส่งรายงานช่องโหว่ในฐานข้อมูลความปลอดภัยอย่าง RustSec บอกไว้เองว่าเขาโดนแบบนี้ตรงๆ คือเห็นคำเตือนแล้วทำตามคำเตือน
245 ล้าน ไม่ใช่จำนวนคนที่โดน
ตัวเลขที่มักปรากฏในพาดหัวข่าวคือยอดดาวน์โหลดสะสมของ arrayref ราว 245 ล้านครั้ง ตัวเลขนี้เป็นเรื่องจริง แต่มันคือยอดรวมตลอดอายุของแพ็กเกจตั้งแต่วันแรกจนถึงวันนี้ ลำพังเวอร์ชัน 0.3.9 ที่ปลอดภัยก็มียอดไปราว 152 ล้านครั้ง ตัวเลขนี้บอกแค่ว่าแพ็กเกจนี้ถูกใช้กันกว้างขวางแค่ไหน ไม่ได้บอกว่ามีกี่เครื่องที่โดนไปด้วย
ช่วงเวลาที่ของอันตรายอยู่บนคลังจริงนั้นสั้นกว่านั้นมาก โดย arrayref 0.3.10 อยู่เพียง 86 นาที ส่วนอีกสองตัวจากเจ้าของเดียวกันที่โดนด้วยคือ append-only-vec 0.1.9 (อยู่ 107 นาที) และ internment 0.8.7 (อยู่ 90 นาที) ขณะที่แพ็กเกจซึ่งคนร้ายสร้างขึ้นมาใหม่ทั้งตัวอย่าง proc-macro1 ก็โดนลบทิ้งหมดทุกเวอร์ชัน เพราะไม่เคยมีเวอร์ชันที่ปลอดภัยอยู่เลย คนที่เข้าเงื่อนไขคือคนที่ build โปรเจกต์ในช่วงเช้าวันนั้นพอดี ไม่ใช่ทุกคนที่เคยใช้ arrayref
อีกเรื่องที่ต้องชี้ให้ชัดเจนคือ ทีมความปลอดภัยของ Rust ระบุว่าไม่เชื่อว่าเจ้าของแพ็กเกจตัวจริงตั้งใจทำเอง แต่น่าจะเป็นบัญชีหรือเครื่องของเขาที่โดนเจาะ ทางทีมจึงล็อกบัญชีไว้ก่อนเพื่อป้องกันความเสียหายระหว่างที่ยังติดต่อเจ้าตัวไม่ได้ การโจมตีรูปแบบนี้รับมือยากมาก เพราะของที่ปล่อยออกมาผ่านทุกการตรวจสอบตามระบบ ลายเซ็นถูกต้อง บัญชีถูกต้อง
สำหรับใครที่มี Rust อยู่ในเครื่อง สามารถใช้คำสั่งนี้ตรวจสอบว่าแพ็กเกจอันตรายเคยลงมาอยู่ในแคชของเครื่องหรือเปล่า
find ~/.cargo/registry/cache -type f \( \
-name 'append-only-vec-0.1.9.crate' -o \
-name 'arrayref-0.3.10.crate' -o \
-name 'internment-0.8.7.crate' -o \
-name 'proc-macro1-*.crate' -o \
-name 'proc-macro-en-*.crate' -o \
-name 'aovine-*.crate' -o \
-name 'arone-*.crate' -o \
-name 'aronenao-*.crate' -o \
-name 'tinymember-*.crate' \
\) -printbuild script ควรมีสิทธิ์แค่ไหน
หลังเหตุการณ์นี้ ข้อถกเถียงในหมู่นักพัฒนาแตกออกเป็นสองฝั่ง และยังไม่มีข้อสรุปที่ชัดเจน
ฝั่งหนึ่งมองว่าต้นตอคือการยอมให้โค้ดแปลกหน้ารันได้ตอน build ตั้งแต่แรก ทางแก้คือจำกัดสิทธิ์ตั้งแต่ต้น ไม่ให้ต่อเน็ต ไม่ให้แตะไฟล์นอกโฟลเดอร์ของตัวเอง หรือให้รันในสภาพแวดล้อมปิดที่ต้องขอสิทธิ์เป็นอย่างๆ ถ้าทำแบบนั้น build script ของ proc-macro1 ก็จะไม่สามารถดาวน์โหลดอะไรได้เลย
อีกฝั่งแย้งว่าการจำกัดสิทธิ์ช่วยลดความเสียหายได้ แต่ไม่ได้แก้ที่ต้นตอ เพราะคนร้ายย้ายโค้ดร้ายไปไว้ในตัวไลบรารีก็ได้ แล้วโค้ดนั้นจะรันตอนโปรแกรมทำงานจริงอยู่ดี และไม่มีการจำกัดสิทธิ์แบบไหนกันเจ้าของบัญชีที่โดนขโมยกุญแจไปแล้วปล่อยของออกมาอย่างถูกต้องตามระบบ
สำหรับคนที่ไม่ได้เขียนเครื่องมือพวกนี้เอง สิ่งที่ได้จากข้อถกเถียงรอบนี้คือ ปัจจุบันยังไม่มีใครมีวิธีปิดช่องโหว่นี้ได้หมด เราจึงยังต้องคอยระวังและตรวจสอบด้วยตัวเองอยู่เสมอว่ากำลังปล่อยให้อะไรรันบนเครื่อง
ครั้งหน้าที่กด yes ให้ agent
เรื่องนี้เกิดขึ้นบน Rust แต่สิ่งที่ต้องปรับตัวนั้นอยู่ในงานประจำวันของเรา โดยไม่จำเป็นต้องรู้ภาษา Rust เลยสักบรรทัด
เวลาที่เครื่องมือหรือ agent เตือนว่าเวอร์ชันนี้เก่าแล้วและให้อัปเดต ให้ดูด้วยว่าเวอร์ชันใหม่เปลี่ยนรายการแพ็กเกจที่ต้องใช้หรือไม่ ไม่ใช่ดูแค่โค้ด ในเคสนี้โค้ดไม่ขยับสักตัวอักษร ของที่เปลี่ยนคือบรรทัดเดียวในไฟล์ตั้งค่า
พอติดตั้งเสร็จ ให้ลองเปิดไฟล์ที่เครื่องมือบันทึกไว้ว่าลงอะไรไปบ้าง ฝั่ง JavaScript คือ package-lock.json ส่วนฝั่ง Python คือไฟล์ requirements.txt ที่ล็อกเวอร์ชันไว้ นั่นคือรายการของจริง ไม่ใช่รายการที่เราขอ จากนั้นกวาดตาหาชื่อที่คล้ายของคุ้นเคยแบบเพี้ยนไปตัวเดียว เพราะนั่นคือรูปแบบที่ทำให้ proc-macro1 หลุดรอดสายตามาได้
ส่วนจังหวะที่ agent ขออนุมัติติดตั้ง นั่นคือการตัดสินใจที่เป็นของเราจริงๆ จะกดผ่านให้งานเดินหน้าเร็วก็ได้ แลกกับการไม่มีใครตรวจดูว่ามีอะไรลงไปบ้าง หรือจะตั้งจังหวะขออนุมัติจากคนก่อนที่ agent จะลงมือ ซึ่งช้าลงแต่ช่วยให้เห็นของทุกชิ้นก่อนมันแตะเครื่อง และถ้าเป็นของที่เพิ่งเคยเห็นชื่อเป็นครั้งแรก เราแนะนำให้ลองติดตั้งในพื้นที่จำลองที่พร้อมลบทิ้งได้ ไม่ใช่เครื่องหลักที่มีกุญแจงานทั้งหมดอยู่ด้วยกัน
รายการของที่เราตั้งใจติดตั้งอาจยาวแค่ไม่กี่บรรทัด แต่รายการของที่ได้รันจริงบนเครื่องเรายาวกว่านั้นเสมอ และช่องว่างระหว่างสองรายการนั้น ก็คือพื้นที่ทำงานของคนร้าย
ที่มา: เอกสารทางการของ Rust
ชอบเรื่องแนวนี้ มีอีบุ๊คฟรีให้อ่านต่อ
ChatGPT Work ฉบับเข้าใจง่าย มอบงานให้ AI ทำจนจบ ตั้งแต่งานแรกจนถึงงานอัตโนมัติ พร้อม workflow ใช้ได้จริง 8 แบบ
กดสมัครแล้วเราจะส่งเทคนิค AI และของแจกใหม่ๆ ให้ทางอีเมล เลิกรับได้ตลอด
Claude Cowork · The Business Playbook

ฉบับภาษาไทย 15 บท เรียนรู้ผ่านโปรเจกต์จำลองต่อเนื่องทั้งเล่ม ตั้งแต่ตั้งค่า Workspace จัดการไฟล์ เชื่อมแอป ตั้งระบบอัตโนมัติ จนถึงสร้าง Plugin


