guest@websec ~ % man 18-intranet-and-admin
18 · ความปลอดภัยของ intranet และระบบ admin
เครื่องมือ admin มีอำนาจมากที่สุด สร้างเร็วที่สุด และถูกตรวจน้อยที่สุด ผู้โจมตีรู้ดี ยึด admin ได้คนเดียวก็ได้ผู้ใช้ทุกคน
ทำไมระบบ admin จึงพิเศษ
หน้า admin ทำได้ทุกอย่าง และแสดงสิ่งที่คนทั่วไปพิมพ์เข้ามา: ticket โปรไฟล์ ชื่อไฟล์ เหมาะกับ XSS มาก
"ระบบภายใน" ไม่ใช่กำแพง เบราว์เซอร์ของพนักงานก็ใช้อ่านอีเมลและท่องเว็บด้วย
18.1 Cross-site scripting ในเครื่องมือ admin
ฟิลด์ที่ไม่ได้ escape ช่องเดียวในหน้า admin ก็พอ สคริปต์รันในนาม admin และแอบสร้าง admin ใหม่ได้
ทางแก้เดียวกับหัวข้อ 04 แต่ต้องใช้กับเครื่องมือภายใน เข้มงวดเท่ากัน
18.2 CSRF ต่อผู้ดูแลระบบ
CSRF ต่อ admin ทำได้ทุกอย่างที่ admin ทำได้ ตัวอย่างจริง: หน้าเว็บที่แอบเปลี่ยนการตั้งค่าเราเตอร์ที่บ้านของคนอื่น
ป้องกันแบบเดียวกับหัวข้อ 07 อย่าปิด CSRF "เพราะเป็นของภายใน"
18.3 ข้อควรระวังเพิ่มเติม
วางแผนสำหรับวันที่บัญชี admin ถูกยึด:
ขอบเขตความเสียหาย
- บทบาท ซัพพอร์ตไม่ต้องมีสิทธิ์ของการเงิน ตรวจที่เซิร์ฟเวอร์ทุกครั้ง
- แยกบัญชี admin ใช้ passkey หรือ security key
- ถามยืนยันอีกครั้ง ก่อนทำสิ่งอันตราย เช่น export หรือเปลี่ยนสิทธิ์
- ใช้ได้เฉพาะผ่าน VPN หรือเครือข่ายออฟฟิศ
- แยก subdomain เช่น
admin.example.comXSS บนเว็บสาธารณะจะเอื้อมไม่ถึงคุกกี้ admin - Audit log + การแจ้งเตือน ทุกการกระทำที่ใช้สิทธิ์สูง
ไม่มีอะไรในตัว gem admin escape ให้ แต่บทบาท จำกัด IP ยืนยันซ้ำ และ audit log เป็นของเรา (ลอง
pundit กับ audited)# config/routes.rb: admin only on its own host constraints(host: "admin.example.com") do namespace :admin do resources :users end end class Admin::BaseController < ApplicationController ADMIN_NET = IPAddr.new("10.0.0.0/8") # VPN range (trust your proxy config) before_action { head :forbidden unless ADMIN_NET.include?(request.remote_ip) } before_action :require_admin_session! # separate admin login + MFA content_security_policy { |p| p.default_src :self; p.script_src :self } end class Admin::RolesController < Admin::BaseController before_action -> { require_recent_auth!(5.minutes) } # your helper: re-prompt def update user = User.find(params[:user_id]) authorize user, :change_role? # Pundit policy user.update!(role: params.require(:role)) AuditLog.create!(actor: Current.admin, action: "role_change", target: user) end end
// Express: lock the admin router down in layers const admin = express.Router(); admin.use(requireNetwork(["10.0.0.0/8"])); // VPN range (behind a trusted proxy) admin.use(requireAdminSession); // separate admin login + MFA admin.use(helmet.contentSecurityPolicy({ directives: { defaultSrc: ["'self'"] } })); admin.post("/users/:id/role", requireRole("superadmin"), requireRecentAuth(5 * 60), async (req, res) => { await audit.log(req.admin.id, "role_change", req.params.id, req.body.role); … }); app.use(vhost("admin.example.com", admin)); // separate host
# Django: staff-only views, permissions, and re-auth for sensitive actions from django.contrib.admin.views.decorators import staff_member_required from django.contrib.auth.decorators import permission_required @staff_member_required @permission_required("accounts.change_role", raise_exception=True) @require_recent_auth(minutes=5) # your decorator: redirect to re-auth def change_role(request, user_id): AuditLog.objects.create(actor=request.user, action="role_change", target=user_id) ... # Serve the admin on its own host, e.g. with django-hosts or a separate # deployment, and restrict it at the proxy (nginx allow/deny, IAP, VPN).