Channel Access Token v2.1 — ใส่เกราะให้บอทแกร่ง 🛡️

Mike Phulsuksombati
LINE Developers Thailand

--

คงไม่ดีแน่ … ถ้า LINE Official Account ของคุณถูก hacker นำไปใช้ในการหลอกลวงลูกค้าเพื่อนำข้อมูลไปใช้ทำสิ่งที่ผิดกฎหมาย

วันนี้เรามาแนะนำการใช้ Channel access token v2.1 เพื่อเพิ่มความปลอดภัย ใส่เกราะ heavy armor อีกชั้นให้กับ LINE Bot ของคุณกัน!!

ก่อนอื่นเรามารู้จักกับ channel access token กันสักนิด (หากคุณรู้จักกับ channel access token แล้วสามารถข้ามไปที่ Channel access token v2.1 ได้เลย)

Channel access token คืออะไร?

Channel access token คือ access token ที่เป็น security credential ในการเรียกใช้ Messaging API นั่นเอง

พูดอีกอย่างคือ channel access token ทำให้ server ของ LINE รู้ว่าการเรียก API นี้ถูก authorize มาจาก channel ไหนเพื่อจะได้ทำงานถูกต้อง ตัวอย่างเช่น เวลา send reply message เราจะต้อง authorize ด้วย channel access token ที่ header ของ request

curl -v -X POST https://api.line.me/v2/bot/message/reply \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer {channel access token}' \
-d '{
"replyToken":"nHuyWiB7yP5Zw52FIkcQobQuGDXCTA",
"messages":[
{
"type":"text",
"text":"Hello, user"
}
]
}'

โดยการจะเรียกใช้ Messaging API จะต้องใช้ channel access token เสมอ

การรักษาความปลอดภัยของ channel access token จึงเป็นเรื่องสำคัญมาก เพราะหากหลุดออกไปสู่มือ hacker ก็เท่ากับว่า hacker จะสามารถเรียกใช้ Messaging API กับ channel เราได้ทั้งหมด hacker อาจจะทำการ broadcast message ไปเพื่อสร้างความเสื่อมเสียให้กับเจ้าของ LINE Official Account หรือนำไปหลอกลวงลูกค้าได้

Channel access token มีกี่ประเภท?

Channel access token ของ LINE แบ่งเป็น 3 ประเภท ตามวันหมดอายุ คือ

  1. Long-lived channel access token
  2. Short-lived channel access token
  3. ✨ User-specified expiration (Channel access token v2.1)

มาลองดูกันทีละตัวนะครับ

Long-lived channel access token

Long-lived channel access token คือ channel access token สำหรับเรียกใช้ Messaging API ที่ไม่มีวันหมดอายุ

เวลาสร้าง LINE bot โดยส่วนมากเราก็จะใช้ long-lived channel access token ชนิดนี้เพื่อความสะดวก ไม่ต้องมาขอ channel access token ใหม่บ่อยๆ เมื่อหมดอายุ โดยสามารถหา long-lived channel access token ได้ที่หน้า Messaging API settings นั่นเอง

Long-lived channel access token จะอยู่ที่ Messaging API settings แถบล่างสุด

Long-lived channel access token จะมีความปลอดภัยต่ำสุด เพราะหาก channel access token หลุดไป (ถูก compromised) hacker จะสามารถเข้าถึง Messaging API จาก channel ของเราได้ตลอดเวลา

หากพบว่า channel access token หลุดไป เราสามารถกดปุ่ม “Reissue” เพื่อ revoke channel access token ตัวเก่า และ issue channel access token ตัวใหม่ เพื่อนำไปใช้ได้

Short-lived channel access token

Short-lived channel access token คือ channel access token สำหรับเรียกใช้ Messaging API ที่มีวันหมดอายุ 30 วัน เพื่อเพิ่ม security นั่นเอง

โดยตัวนี้เราจะต้อง issue ผ่าน API เท่านั้น โดยใช้ channel ID และ channel secret

curl -v -X POST https://api.line.me/v2/oauth/accessToken \
-H 'Content-Type:application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=client_credentials' \
--data-urlencode 'client_id={channel ID}' \
--data-urlencode 'client_secret={channel secret}'

โดยเราจะได้ access_token ที่มีอายุ 30 วัน (2,592,000 วินาที) กลับมาเพื่อไปใช้ต่อ

{
"access_token": "W1TeHCgfH2Liwa.....",
"expires_in": 2592000,
"token_type": "Bearer"
}

โดยทั่วไปเราจะไม่ค่อยได้ใช้ channel access token ตัวนี้เท่าไหร่ แต่สำหรับ Official Account ที่ต้องการ security สูงเช่น account ของธนาคารจะใช้ short-lived channel access token เพื่อเพิ่มความปลอดภัย เพราะหาก channel access token ถูก compromised hacker จะสามารถใช้งาน token ตัวนี้ได้ภายในเวลาไม่เกิน 30 วันนั่นเอง

หากพบว่า channel access token หลุดไป เราก็สามารถ revoke ผ่าน API ได้เช่นกัน

curl -v -X POST https://api.line.me/v2/oauth/revoke \
-H "Content-Type: application/x-www-form-urlencoded" \
--data-urlencode 'access_token={channel access token}'

และเราก็มาถึงเกราะ heavy armor v2.1 🛡️ ล่าสุดที่จะมาเสริมความปลอดภัยอีกชั้นให้กับบอทของคุณ …

✨ User-specified expiration channel access token ✨ (Channel access token v2.1 )

Channel access token v2.1 คือ channel access token สำหรับเรียกใช้ Messaging API ที่สามารถให้ developer สามารถกำหนดวันหมดอายุได้ด้วยตัวเอง

โดยมีการเพิ่ม security ด้วยการใช้ JSON Web Token (JWT) แทนที่ channel secret ของ short-lived channel access token

โดยตัวนี้เราจะต้อง issue ผ่าน API เท่านั้น โดยส่ง JWT ที่กำหนดวันหมดอายุของ channel access token ไป

curl -X POST https://api.line.me/oauth2/v2.1/token \
-H 'Content-Type:application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=client_credentials' \
--data-urlencode 'client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer' \
--data-urlencode 'client_assertion={JWT}'

ส่วนการสร้าง JWT จะต้องไปกดสร้าง RSA Key Pair ที่หน้า Basic settings ก่อน จะได้ public key มาหน้าตาประมาณนี้

เสร็จแล้วจะมีให้ download private key ลงเครื่อง เพราะ LINE จะไม่เก็บ private key ไว้ โดย private key ที่ได้มาจะอยู่ในรูป JSON Web Key (JWK) format ให้สังเกตว่า kid (หรือ Key Id) จะตรงกับ public key ของเราที่หน้า Basic settings

ซึ่งการสร้าง JWT นั้นสามารถทำได้หลายวิธี โดยในบทความนี้ผมจะ sign ด้วย library jsonwebtoken (สามารถดูได้ที่ documentation ของ LINE สำหรับวิธีอื่นๆ)

ขั้นแรกเราจะต้องสร้าง header ของ JWT ตามนี้ โดยให้ kid เป็น public key ของเรา

และสร้าง payload ต่อ โดยเราจะกำหนด 2 ตัวที่สำคัญคือ token_exp เวลาหมดอายุของ access token โดยผมจะตั้งไว้ 1 วัน และ exp เวลาหมดอายุของ JWT ซึ่งโดยทั่วไปจะตั้งไว้ให้หมดอายุภายใน 15 นาทีหลัง issue

เมื่อได้ header และ payload แล้วเรามาดู code ส่วย sign ด้วย lib jsonwebtoken กัน เนื่องจาก key pair ที่ได้จาก LINE จะเป็น JWK format เราจึงจะต้องแปลงเป็น PEM format เพื่อให้ใช้กับ jsonwebtoken เพื่อให้ sign ด้วย RS256 ได้

เราจะได้ public key ในรูปแบบ PEM format ออกมาข้างล่าง

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAiIguY9TT5M3UPHxjgr+3
TaEEtAIh4AhOepuDE694wVq6G/BmeE5ZX3hqJX3YTmVPQuFIH+B5lJNQz4bcBn/6
jIPxzm75lqk/FQoBcV2uhpnGhDla9p47oQT1HtCpybZIREDD6INpwnb7OmUbRcB4
6YSzIJlJ9qOYXUrGXQQcCHv+TB+dmBp/4CFcEZerHwh7i7LTanztV53z4wVnLBRi
HCz3HWPVT58nPwgpW9yUMUagLFAn7ukMiWnjGatSvPPD5XRrKrJ4fml+it0GCkbZ
zcyRB85amWyyLbClWCZEVUATn1ooWXNaxShVgFz0HZ78T3I8sR/kt+2Uc+MVyFi3
7wIDAQAB
-----END PUBLIC KEY-----

และได้ JWT ตามนี้

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjRkYTEzZGQ2LTI4MTQtNDFhOC1hMzUxLTFiMGRmYjBhODBjNyJ9.eyJ0b2tlbl9leHAiOjg2NDAwLCJleHAiOjE1ODY2MjAyMDcsImF1ZCI6Imh0dHBzOi8vYXBpLmxpbmUubWUvIiwiaXNzIjoiMTYxNDU2NDAxMiIsInN1YiI6IjE2MTQ1NjQwMTIifQ.U6BXmlIPRoUCx4xjRqqqoEWG9wyXb1J9r9ovSidBjyp0HLGpcYZaDAS4HPim1Mv28LrOFjMoEKDh0qPTpllX-DNBei2ce95cKEdZ5jfxzdO2sGPNKhdGTCr4c4WWdCpCED485yJZxeT1vC4F3Yx0tlg_6FTdWYXrSLJAlsFDtvhnsbduX7GFywqUpW66J8uAX8ZDuwBzJP-H9efHbVZlfpdC4t9yrPXuXeES-WII86HCLaxyV_bEjPj6OAZUKW77-cIzQzHugrc_P7hgA5eSztWqCPdX-hz4LODSyf66fCIJ-Krm2D0mh8-TdU8xmTwnEiz3-f8eAG5ZCcWZk_2ysw

ลองเอาไป decode ดูที่ https://jwt.io/ จะได้ว่า header และ payload ออกตรงกับที่เราตั้งไว้

และเมื่อนำ public key มา verify signature จะได้ว่า signature ออกมาถูกต้อง

และเมื่อเรานำ JWT ที่ได้มา ยิง request ไปขอ access_token ใหม่

curl -X POST https://api.line.me/oauth2/v2.1/token \
-H 'Content-Type:application/x-www-form-urlencoded' \
--data-urlencode 'grant_type=client_credentials' \
--data-urlencode 'client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer' \
--data-urlencode 'client_assertion=eyJhbGciOiJSUzI1Ni...'

เราก็จะได้ access_token กลับมานั่นเอง สังเกตว่า access_token จะมีอายุ 1 วัน (86400 วินาที) ตามที่เรากำหนดนั่นเอง!

{
"access_token": "eyJhbGciOiJIUz.....",
"expires_in": 86400,
"token_type": "Bearer"
}

(โดย access_token ที่กลับมาจะเป็น JWT เช่นกัน แต่ encoded ด้วย HS256)

จะเห็นว่า Channel access token v2.1 มีความปลอดภัยมากกว่า short-lived channel access token ไม่ใช่แค่เพราะว่าเราสามารถกำหนดวันหมดอายุให้สั้นกว่า 30 วันได้ แต่เพราะการนำ JWT มาใช้ประกอบในการขอ token ด้วย (ทำไมลองดูข้างล่าง)

และเช่นเดียวกับ short-lived channel access token หาก access_token หลุดไป เราก็สามารถ revoke ได้ด้วย API เช่นกัน แต่จะต้องระบุ channel id และchannel secret เพิ่ม

curl -X POST https://api.line.me/oauth2/v2.1/revoke \
--data-urlencode 'client_id={channel ID}' \
--data-urlencode 'client_secret={channel secret}' \
--data-urlencode 'access_token={access token}'

ทำไมถึงถึงเปลี่ยนมาใช้ JWT แทน Channel Secret?

มาลองสวมหมวก hacker เพื่อโจมตีบอทที่ใช้ Short-lived channel access token กับ Channel access token v2.1 กัน

⚔️ Short-lived channel access token

สมมติว่าเราจะ attack ตัว short-lived channel access token

และเราสามารถเข้าถึง client สักพักนึงก่อนจะถูก detect และเตะออกมา จนเราได้ access_token และ channel secret มา (client ต้องถือ channel secret ไว้เพื่อส่งให้ API)

ดังนั้น

  • access_token จะใช้ได้ไม่เกิน 30 วัน
  • channel secret สามารถใช้ขอ access_token ได้ตลอดเวลา ☠️

ซึ่งอันตรายมาก เพราะเปิด period ให้ hacker เข้าถึง Messaging API ของ channel เราตลอดเวลาจนกว่าจะ detect และ revoke channel secret ไป

⚔️ Channel access token v2.1

สมมติว่าเราจะ attack ตัว Channel access token v2.1 โดย private key ถูกเก็บไว้ที่ authentication server ที่เป็น no internet zone

และเราสามารถเข้าถึง client สักพักนึงก่อนจะถูก detect และเตะออกมา จนเราได้ access_token และ JWT มา (client ต้องถือ JWT ไว้เพื่อส่งให้ API)

ดังนั้น

  • access_token จะใช้ได้ไม่เกินเวลาที่กำหนด
  • JWT สามารถใช้ขอ access_token ในระยะเวลาไม่เกิน 15 นาที

ซึ่งเปิด period ให้ hacker เข้าถึง Messaging API ของ channel เราถึงระยะเวลาที่ access_token หมดอายุ (เพราะว่า JWT หมดอายุในแค่ 15 นาที) จะไม่สามารถเข้าได้ตลอดเวลา

ดังนั้นถ้า hacker อยากเข้าถึง access_token ได้โดยไม่จำกัดเวลา จะต้อง

  1. เข้าถึง client ให้ได้เป็นระยะเวลานาน และสั่งขอ JWT มาเรื่อยๆ แต่ทั้งนี้ก็ไม่สามารถแก้ไข JWT เพื่อเพิ่มเวลาหมดอายุ access_token ได้เพราะถูก sign มาจาก auth server
  2. เข้ามาที่ตัว auth server ของเรา แล้วเข้ามาเอา private key ไป

ซึ่งถ้าโดน 2 กรณีนี้ยังไงก็จบเห่ ☠️ แต่ก็ยากกว่าการเข้ามาเอา Channel Secret ในกรณีของ short-lived channel access token มาก

สรุป

หากคุณกำลังพัฒนา LINE Official Account ของบริษัท ที่ต้องการความปลอดภัยสูง และกำลังใช้ Long-lived channel access token อยู่ ควรเปลี่ยนมาใช้ Channel access token v2.1 เพื่อเพิ่มความปลอดภัยและลดโอกาสในการถูกนำ Channel ของคุณไปใช้ในการทำสิ่งที่ผิด #ป้องกันไว้ชัวร์กว่า 💚

อ้างอิง

https://developers.line.biz/en/docs/messaging-api/channel-access-tokens/#what-are-channel-access-tokens

--

--