Compare commits

...

45 Commits

Author SHA1 Message Date
Felicia Seo
64ebfbf313 .css, .svg 2022-12-11 20:01:25 -05:00
Felicia Seo
f82dce4835 partner.json 2022-12-11 19:13:04 -05:00
Felicia Seo
269ac0c501 comments 2022-12-11 19:10:09 -05:00
Felicia Seo
f93671ac58 extra cookie 2022-12-11 18:55:53 -05:00
Felicia Seo
152cd33d83 95/95git add http.c socket.c! 2022-12-11 18:51:41 -05:00
Micah Moore
00151f2e18 77/95 2022-12-11 17:12:25 -05:00
Felicia Seo
aeb235246d ~73/95 2022-12-11 12:23:40 -05:00
Felicia Seo
a874b62771 logout, headers 2022-12-10 20:41:03 -05:00
Felicia Seo
4720b6f233 validate_token_exp, private 2022-12-09 17:27:52 -05:00
Micah Moore
af936cb46e vunerability fix in handle_static_asset 2022-12-09 14:38:17 -05:00
Felicia Seo
724afe49c1 post login, post logout, multiclient 2022-12-08 08:48:09 -05:00
Micah Moore
5516fe8910 html5 fallback 2022-12-07 20:20:33 -05:00
Micah Moore
45517a03cf threads in main.c, handling video 2022-12-07 19:10:50 -05:00
Micah Moore
6b84d45674 completed get_handle_login 2022-12-07 18:38:58 -05:00
Micah Moore
7f4a319353 support logout in React app frontend, update to 0.2.1 2022-12-07 18:13:22 -05:00
Micah Moore
991fc01c87 get_handle_login; token field in http_transaction 2022-12-05 20:24:32 -05:00
Micah Moore
bbb018471f added val_api_url and updated handle_api 2022-12-05 18:37:33 -05:00
Godmar Back
ad249906f4 Merge branch 'master' of git.cs.vt.edu:cs3214-staff/pserv 2022-11-17 01:11:40 -05:00
Godmar Back
06153db5c1 handout Fall'22
- added Jansson examples to jwt_demo_hs256
- added cookie property tests
2022-11-17 01:10:53 -05:00
cwshugg
a5525414c8 small fix for a test's feedback 2022-04-28 11:52:38 -04:00
cwshugg
721256a343 added onto JSON-claim-checking test (for /api/login) in order to verify servers correctly send JSON claims on a request to GET /api/login with a valid cookie. Also, modified the range-request-checking function to search for the Accept-Range header ONLY in requests to static files 2022-04-27 21:41:42 -04:00
cwshugg
21327b0f76 tweaked feedback for test_two_connections, test_four_connections, and test_eight_connections, to ensure students know they require HTTP/1.1 persistent connection support 2022-04-27 21:18:03 -04:00
cwshugg
c4c35394f4 fixed various testing issues with 'test_video_range_request', 'test_accept_ranges_header', and added a new test to ensure students are sending the correct Content-Type in responses to /api/login requests ('test_login_content_type') 2022-04-27 18:45:44 -04:00
cwshugg
43dbbe35cf minor bugfix in error handling for video streaming test, and a message printed when '-t' can't find a specified test 2022-04-26 14:36:46 -04:00
Godmar Back
be04f7d339 update to current npm versions (React 18, BS5, etc.) 2022-04-19 09:53:48 -04:00
cwshugg
5555adff24 moved tests between the 'extra' and 'malicious' category, and added a new check for whether or not malicious tests should be run 2022-03-29 08:45:31 -04:00
cwshugg
83ef85d764 quick spacing adjustment 2022-03-28 21:32:37 -04:00
cwshugg
0a94822e39 changed final score printing to use the same set of test names 2022-03-28 21:31:42 -04:00
cwshugg
eca7ce2ff1 fixed issue with new 'verbose' switch and adjusted test categories to print more accurately when running with '-l' 2022-03-28 21:18:18 -04:00
cwshugg
005efe2f5e added verbose option (new default puts stdout and stderr in /dev/null) 2022-03-28 20:10:48 -04:00
cwshugg
5d9c9688d3 added variation in the combination and order of cookies used in test_auth_wrong_cookie2 2022-03-22 16:47:19 -04:00
cwshugg
e5e78f2682 added test that uses real cookies from web browsers to test a server 2022-03-22 16:40:39 -04:00
cwshugg
04887c2180 changed unit test script to use SIGTERM instead of SIGKILL 2022-03-22 15:54:07 -04:00
cwshugg
e5f3678703 changed the unit tests to print a total 2022-03-22 15:52:38 -04:00
cwshugg
f2b847c9a8 finished implementing the video streaming tests 2022-03-22 15:43:00 -04:00
cwshugg
65de51ebee added video files necessary for the video tests 2022-03-22 09:17:30 -04:00
cwshugg
859c3d0e6c added the beginnings of a suite of video-related tests 2022-03-22 00:05:09 -04:00
cwshugg
74e9d678f9 implemented HTML5 fallback tests 2022-03-21 15:31:22 -04:00
cwshugg
05799697e2 added new gurthang documentation 2022-03-21 14:17:46 -04:00
Godmar Back
f7986a3780 Merge branch 'master' of git.cs.vt.edu:cs3214-staff/pserv 2021-12-06 14:41:54 -05:00
Godmar Back
7d04db7bb3 corrected instructions 2021-12-06 14:41:26 -05:00
gback
87ee059c92 Merge branch 'fall2021_fuzz' into 'master'
Fall 2021 Fuzzing Documentation Changes

See merge request cs3214-staff/pserv!62
2021-11-16 23:33:08 +00:00
cwshugg
d259c13c0b fall 2021 SFI documentation changes 2021-11-16 14:32:42 -05:00
Godmar Back
8584da6a7f added comments to testloginapi.sh 2021-11-16 10:58:21 -05:00
Godmar Back
966e23dd13 updated react packages to 2021
- exception: Bootstrap remains at 4, react-router-dom at 5.*
2021-11-16 10:45:06 -05:00
56 changed files with 16597 additions and 28248 deletions

View File

@ -19,7 +19,7 @@ git clone https://github.com/akheron/jansson.git
git clone https://git@github.com/benmcollins/libjwt.git git clone https://git@github.com/benmcollins/libjwt.git
(cd libjwt; (cd libjwt;
git checkout v1.13.1; git checkout v1.14.0;
autoreconf -fi; autoreconf -fi;
env PKG_CONFIG_PATH=../deps/lib/pkgconfig:${PKG_CONFIG_PATH} ./configure --prefix=${BASE}/deps; env PKG_CONFIG_PATH=../deps/lib/pkgconfig:${PKG_CONFIG_PATH} ./configure --prefix=${BASE}/deps;
make -j 40 install make -j 40 install

21
react-app/.gitignore vendored
View File

@ -1,21 +1,2 @@
# See https://help.github.com/ignore-files/ for more about ignoring files.
# dependencies
/node_modules /node_modules
.gitignore
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

View File

@ -23,9 +23,12 @@ Once you've made this addition, you can test the app as follows:
$ ssh -L 10000:localhost:yourport you@rlogin.cs.vt.edu $ ssh -L 10000:localhost:yourport you@rlogin.cs.vt.edu
(2) Add node to your PATH: (2) Make sure that ~cs3214/bin is in your PATH so you have access to node and npm
$ export PATH=/home/courses/cs3214/software/node-v8.11.1-linux-x64/bin:$PATH ```
$ which node
/home/courses/cs3214/bin/node
```
(3) Inside react-app, run (3) Inside react-app, run

42231
react-app/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,23 +1,26 @@
{ {
"name": "react-app", "name": "react-app",
"version": "0.2.0", "version": "0.2.1",
"private": true, "private": true,
"dependencies": { "dependencies": {
"bootstrap": "^4.6.0", "bootstrap": "^5.2.2",
"formik": "^2.2.6", "formik": "^2.2.9",
"prop-types": "^15.6.1", "prop-types": "^15.8.1",
"react": "^17.0.2", "react": "^18.2.0",
"react-dom": "^17.0.2", "react-dom": "^18.2.0",
"react-player": "^2.9.0", "react-player": "^2.10.0",
"react-redux": "^7.2.3", "react-redux": "^8.0.5",
"react-router-dom": "^5.2.0", "react-router": "^6.4.3",
"react-scripts": "4.0.3", "react-router-dom": "^6.4.3",
"reactstrap": "^8.9.0", "reactstrap": "^9.1.5",
"redux": "^4.0.5", "redux": "^4.1.2",
"redux-thunk": "^2.3.0", "redux-thunk": "^2.4.0",
"superagent": "^6.1.0", "superagent": "^8.0.3",
"toastr": "^2.1.4" "toastr": "^2.1.4"
}, },
"devDependencies": {
"react-scripts": "^5.0.1"
},
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",
"build": "react-scripts build", "build": "react-scripts build",

View File

@ -1,7 +1,11 @@
https://reacttraining.com/react-router/web/guides/redux-integration
Updated Spring 2020 to React 16.13.1 and react-redux 7.2.0 Updated Spring 2020 to React 16.13.1 and react-redux 7.2.0
Updated Spring 2021 to React 17.0.2 and latest version of all other packages. Updated Spring 2021 to React 17.0.2 and latest version of all other packages.
- added support for video player - added support for video player
Updated Spring 2022 to React 18.0.0 and latest version of all other packages.
- updated bootstrap to BS5 and react-router v6
https://reacttraining.com/react-router/web/guides/redux-integration

View File

@ -31,3 +31,19 @@ export function checklogin() {
}, },
}); });
} }
export function logout() {
return apiAction({
baseType: 'LOGOUT',
fetch() {
return api.auth.logout();
},
onSuccess(dispatch, data, getState) {
toastr.success(`Success logging out: ${JSON.stringify(data)}`);
},
onError(dispatch, data, getState) {
toastr.success(`Error logging out: ${JSON.stringify(data && data.response && data.response.text)}`);
},
});
}

View File

@ -1,14 +1,23 @@
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { Redirect } from 'react-router-dom'; import { Navigate } from 'react-router-dom';
import store from '../store'; import { useDispatch, useSelector } from 'react-redux';
import { logout } from '../actions/auth.js';
import { isLoaded } from '../util/loadingObject'
const Logout = () => { const Logout = () => {
// normally, we would inform the server just in case. const dispatch = useDispatch();
document.cookie = "auth_token="; const user = useSelector(state => state.auth);
const isAuthenticated = isLoaded(user);
// log out on render, and navigate to root on success
useEffect(() => { useEffect(() => {
store.dispatch({ type: "LOGOUT" }); dispatch(logout());
}, []); }, []);
return (<Redirect to="/" />);
if (isAuthenticated)
return (<i>Logging you out ....</i>);
else
return (<Navigate to="/" />);
}; };
export default Logout; export default Logout;

View File

@ -38,10 +38,10 @@ const DropDowns = (props) => {
<DropdownToggle nav caret> <DropdownToggle nav caret>
{dropdown.label} {dropdown.label}
</DropdownToggle> </DropdownToggle>
<DropdownMenu right> <DropdownMenu end>
{dropdown.entries.map((item) => {dropdown.entries.map((item) =>
<DropdownItem key={item.path}> <DropdownItem key={item.path}>
<NavLink to={item.path} key={item.path} activeClassName="active" tag={RRNavLink}> <NavLink to={item.path} key={item.path} tag={RRNavLink}>
{item.label} {item.label}
</NavLink> </NavLink>
</DropdownItem> </DropdownItem>
@ -70,7 +70,7 @@ const NavBar = (props) => {
<Nav className="mr-auto" navbar> <Nav className="mr-auto" navbar>
{menus.topbar.map((item) => {menus.topbar.map((item) =>
<NavItem key={item.path}> <NavItem key={item.path}>
<NavLink to={item.path} activeClassName="active" tag={RRNavLink}> <NavLink to={item.path} tag={RRNavLink}>
{item.label} {item.label}
</NavLink> </NavLink>
</NavItem> </NavItem>
@ -86,11 +86,11 @@ const NavBar = (props) => {
{isLoaded(user) ? {isLoaded(user) ?
<NavItem> <NavItem>
<NavLink activeClassName="active" tag={RRNavLink} to={props.logoutUrl}>Logout ({user.sub})</NavLink> <NavLink tag={RRNavLink} to={props.logoutUrl}>Logout ({user.sub})</NavLink>
</NavItem> </NavItem>
: :
<NavItem> <NavItem>
<NavLink activeClassName="active" tag={RRNavLink} to={props.loginUrl}>Login</NavLink> <NavLink tag={RRNavLink} to={props.loginUrl}>Login</NavLink>
</NavItem> </NavItem>
} }
</Nav> </Nav>

View File

@ -54,9 +54,10 @@ const LoginForm = (props) => {
<ButtonToolbar> <ButtonToolbar>
<Button <Button
type='submit' type='submit'
bsstyle='success' className="mr-2"> bsstyle='success'>
Submit Submit
</Button> </Button>
&nbsp;
<Button <Button
type="button" type="button"
onClick={handleReset} onClick={handleReset}

View File

@ -34,4 +34,7 @@ const apiPrefix = `${publicUrl}/api`
console.log(`Read configuration. Public_URL: ${publicUrl}`) console.log(`Read configuration. Public_URL: ${publicUrl}`)
// console.log(`apiPrefix: ${apiPrefix}`) // console.log(`apiPrefix: ${apiPrefix}`)
export default { menus, apiPrefix, publicUrl } export default { menus,
branding: "CS3214 Demo App 2022",
apiPrefix, publicUrl
}

View File

@ -9,9 +9,9 @@ import HomePage from '../pages/HomePage';
import PrivatePage from '../pages/PrivatePage'; import PrivatePage from '../pages/PrivatePage';
import PlayerPage from '../pages/PlayerPage'; import PlayerPage from '../pages/PlayerPage';
import { Switch, Route, withRouter } from 'react-router-dom'; import { Routes, Route } from 'react-router-dom';
import config from '../config/'; import config from '../config';
/** AppContainer renders the navigation bar on top and its /** AppContainer renders the navigation bar on top and its
* children in the main part of the page. Its children will * children in the main part of the page. Its children will
@ -19,22 +19,22 @@ import config from '../config/';
*/ */
const AppContainer = (props) => ( const AppContainer = (props) => (
<div> <div>
<TopNavBar branding="CS3214 Demo App 2020" <TopNavBar branding={config.branding}
menus={config.menus} menus={config.menus}
user={props.user} user={props.user}
loginUrl={`/login`} loginUrl={`/login`}
logoutUrl={`/logout`} logoutUrl={`/logout`}
/> />
<div className="container-fluid marketing"> <div className="container-fluid marketing">
<Switch> <Routes>
<Route exact path={`/`} component={HomePage} /> <Route exact path={`/`} element={<HomePage />} />
<Route path={`/logout`} component={Logout} /> <Route path={`/logout`} element={<Logout />} />
<Route path={`/login`} component={LoginPage} /> <Route path={`/login`} element={<LoginPage />} />
<Route path={`/public`} component={PublicPage} /> <Route path={`/public`} element={<PublicPage />} />
<Route path={`/protected`} component={PrivatePage} /> <Route path={`/protected`} element={<PrivatePage />} />
<Route path={`/player`} component={PlayerPage} /> <Route path={`/player`} element={<PlayerPage />} />
<Route component={NotFoundPage} /> <Route element={<NotFoundPage />} />
</Switch> </Routes>
</div> </div>
</div> </div>
); );
@ -51,4 +51,4 @@ function mapDispatchToProps(dispatch) {
}; };
} }
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(AppContainer)); export default connect(mapStateToProps, mapDispatchToProps)(AppContainer);

View File

@ -4,33 +4,27 @@
*/ */
import React from 'react'; import React from 'react';
import { Redirect, withRouter } from 'react-router'; import { Navigate, useLocation } from 'react-router';
import { connect } from 'react-redux'; import { useSelector } from 'react-redux';
import { isLoaded } from '../util/loadingObject'; import { isLoaded } from '../util/loadingObject';
function mapStateToProps(state) {
return {
user: state.auth
};
}
export default function RequireAuthentication(Component) { export default function RequireAuthentication(Component) {
const wrapper = props => { const wrapper = props => {
if (isLoaded(props.user)) { const location = useLocation();
const user = useSelector(state => state.auth);
if (isLoaded(user)) {
return <Component {...props} />; return <Component {...props} />;
} else { } else {
return ( return (
<Redirect <Navigate
to={{ to={`/login`}
pathname: `/login`, state = {{
state: { from: location
from: props.history.location }}
}
}}
/> />
); );
} }
}; };
return withRouter(connect(mapStateToProps)(wrapper)); return wrapper;
} }

View File

@ -1,5 +1,6 @@
import React from 'react'; import React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import { createRoot } from 'react-dom/client';
import { Provider } from 'react-redux'; import { Provider } from 'react-redux';
import { BrowserRouter as Router } from 'react-router-dom'; import { BrowserRouter as Router } from 'react-router-dom';
@ -22,6 +23,7 @@ toastr.options.positionClass = 'toast-bottom-right';
store.dispatch(checklogin()); store.dispatch(checklogin());
const mountPoint = document.getElementById('root'); const mountPoint = document.getElementById('root');
const root = createRoot(mountPoint);
const rootNode = ( const rootNode = (
<Provider store={store}> <Provider store={store}>
<Router basename={config.publicUrl}> <Router basename={config.publicUrl}>
@ -29,6 +31,6 @@ const rootNode = (
</Router> </Router>
</Provider> </Provider>
); );
ReactDOM.render(rootNode, mountPoint); root.render(rootNode);
window.jQuery = jQuery; // for toastr window.jQuery = jQuery; // for toastr

View File

@ -5,9 +5,10 @@ import { Card, CardHeader, CardBody, Container, Row, Col } from 'reactstrap';
import { login } from '../actions/auth.js'; import { login } from '../actions/auth.js';
import { isLoading, isLoaded } from '../util/loadingObject' import { isLoading, isLoaded } from '../util/loadingObject'
import LoginForm from '../components/forms/LoginForm'; import LoginForm from '../components/forms/LoginForm';
import { Redirect } from 'react-router-dom'; import { Navigate, useLocation } from 'react-router-dom';
const LoginPage = (props) => { const LoginPage = () => {
const location = useLocation();
const dispatch = useDispatch(); const dispatch = useDispatch();
function doLogin({username, password}) { function doLogin({username, password}) {
dispatch(login(username, password)); dispatch(login(username, password));
@ -15,9 +16,9 @@ const LoginPage = (props) => {
const user = useSelector(state => state.auth); const user = useSelector(state => state.auth);
const isAuthenticated = isLoaded(user); const isAuthenticated = isLoaded(user);
const { from } = props.location.state || { from: { pathname: "/" } }; const { from } = location.state || { from: { pathname: "/" } };
if (isAuthenticated) { if (isAuthenticated) {
return (<Redirect to={from} />); return (<Navigate to={from} />);
} }
return ( return (

View File

@ -14,8 +14,8 @@ const PlayerPage = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();
let videos = useSelector((state) => state.video); const videos = useSelector((state) => state.video);
let [state, setState] = useState({ const [state, setState] = useState({
url: "" url: ""
}); });
const handleChange = (event) => { const handleChange = (event) => {
@ -61,7 +61,10 @@ const PlayerPage = () => {
</ButtonToolbar> </ButtonToolbar>
</Col> </Col>
<Col> <Col>
or select one from the list of <FormGroup>
<Label for="ddmenu">
or select one from the list of
</Label>
{!isLoaded(videos) ? ( {!isLoaded(videos) ? (
<Spinner size="lg" color="primary" /> <Spinner size="lg" color="primary" />
) : ( ) : (
@ -79,6 +82,7 @@ const PlayerPage = () => {
</DropdownMenu> </DropdownMenu>
</Dropdown> </Dropdown>
)} )}
</FormGroup>
</Col> </Col>
</Row> </Row>
</Container> </Container>

View File

@ -9,16 +9,19 @@ const PrivatePage = () => {
return (<Container> return (<Container>
<h1>Welcome to a private page</h1> <h1>Welcome to a private page</h1>
<Row className="mt-3"> <Row className="mt-3">
You have successfully authenticated as user&nbsp;<span>{user.sub}</span>. <p>
You have successfully authenticated as user&nbsp;<tt>{user.sub}</tt>.
</p>
</Row> </Row>
<Row className="mt-3"> <Row className="mt-3">
Your token was issued at {new Date(user.iat*1000).toString()}, <p>Your token was issued at {new Date(user.iat*1000).toString()},
it expires {new Date(user.exp*1000).toString()} it expires {new Date(user.exp*1000).toString()}</p>
</Row> </Row>
<Row className="mt-3"> <Row className="mt-3">
This page is "private" only inasmuch as the front-end does not <p> This page is "private" only inasmuch as the front-end does not
display it to unauthenticated users. In a fully-fledged app, display it to unauthenticated users. In a fully-fledged app,
this page would now perform API requests that require authentication. this page would now perform API requests that require authentication.
</p>
</Row> </Row>
</Container>); </Container>);
} }

View File

@ -29,7 +29,7 @@ export default function(state = initialState, action) {
if (!('sub' in newState)) if (!('sub' in newState))
delete newState.loadingStatus; delete newState.loadingStatus;
} }
if (action.type === 'LOGOUT') { if (action.type.startsWith('LOGOUT:')) {
newState = { } newState = { }
} }

33
sfi/after_fuzzing.md Normal file
View File

@ -0,0 +1,33 @@
# What do I do after fuzzing?
Once you've completed a fuzzing run, you'll most likely have a few output files whose contents caused your server to crash or hang. (If the fuzzer didn't report any, congratulations! Your server must be pretty robust.) Each of these files contains the input that was sent to your server that caused the issue. From the example shown in `how_to_fuzz.md`, we can see a few of the crash files in `./fuzz_out_2021-11-16_09-42-39/fuzz1/crashes`:
![](./images/img_fuzz_results_screenshot1.png)
The `LD_PRELOAD` library ("gurthang") developed for this purpose uses a special file format to represent several connections' data in a single run. Because of this, sending the file straight to your server won't reproduce the exact behavior found by the fuzzer.
(If you'd like to see the details of one of these **comux** files, run `~cs3214/bin/comux -s -i PATH_TO_FILE [-v]` on one to show a summary of how many connections are represented in the file, and what data will be sent to the server.)
Let's look at `./fuzz_out_2021-11-16_09-42-39/fuzz1/crashes/id:000000,sig:11,src:000188+000106,time:86,ss_chunk_havoc`. Based on the file's name (we can see `sig:11`), the fuzzer indicated the server crashed by receiving a SIGSEGV signal (a segmentation fault) when this file's contents were sent to the server.
## Reproducing a Crash or Hang
Once the fuzzer has found a bug in your code, the next logical step would be to reproduce it and debug. This is made easy by the scripts generated inside the fuzzer's output directory (`fuzz-rerun.sh` and `fuzz-rerun-gdb.sh`) Let's say we want to try recreating the supposed SIGSEGV the server received when that file's contents were sent to the server.
We can take the command straight from the fuzzing summary and modify it to point to the file we're interested in:
```bash
$ ./fuzz_out_2021-11-16_09-42-39/fuzz-rerun.sh ./fuzz_out_2021-11-16_09-42-39/fuzz1/crashes/id:000000,sig:11,src:000188+000106,time:86,ss_chunk_havoc
```
![](./images/img_fuzz_results_screenshot2.png)
As promised, a segmentation fault occurred! Now, let's try running it in GDB:
```bash
$ ./fuzz_out_2021-11-16_09-42-39/fuzz-rerun-gdb.sh ./fuzz_out_2021-11-16_09-42-39/fuzz1/crashes/id:000000,sig:11,src:000188+000106,time:86,ss_chunk_havoc
```
![](./images/img_fuzz_results_screenshot3.png)
Again, the SIGSEGV occurred. From here, you can debug in GDB to discover the source of your bug.

View File

@ -10,11 +10,11 @@ Now, to make sure this fruit slicer works correctly, you decide it's best to tes
![](./images/img_fuzzing_analogy1.png) ![](./images/img_fuzzing_analogy1.png)
At this point, you might think your machine is perfect: it cuts every kind of fruit flawlessly. Enter, fuzzing. What happens if you put something into the fruit slicer that it isn't expecting? Some unexpected inputs might be: water, bread, a paper towel, a rock, or a spoon. These aren't fruits, and some of them aren't even food. Will your machine break? At this point, you might think your machine is perfect: it cuts every kind of fruit flawlessly. Enter, fuzzing. Take the fruit and surround it by a bunch of rocks, then glue it together and bake it in the oven for twelve hours. We've "mutated" the original input to be something odd and unexpected. Will your machine break?
![](./images/img_fuzzing_analogy2.png) ![](./images/img_fuzzing_analogy2.png)
By fuzzing, we can uncover bugs that might later be revealed by attackers or flawed inputs. Identifying such bugs and fixing them will make our programs more robust and secure. Now, mutate something else (or the same input a second time), and try it again. Now do it again. Over and over. This is fuzzing: creating unexpected inputs and feeding it to our target repeatedly, in some automated fashion. By fuzzing, we can uncover bugs that might later be revealed by attackers or flawed inputs. Identifying such bugs and fixing them will make our programs more robust and secure.
## Fuzzing HTTP Servers ## Fuzzing HTTP Servers
@ -37,4 +37,4 @@ User-Agent: Mozilla/4.0
Conn---ection: Keep-Alivey Conn---ection: Keep-Alivey
``` ```
As is common with most fuzzers, the input was "mutated" to produce this odd, incorrect HTTP request message. Will your server see the `GONT` and recognize it as an invalid method? Will it realize `HTTP/1.111` is an invalid version number? If your program handles this badly, and ends up crashing or doing something it shouldn't, then fuzzing was a success. An attacker could take advantage of this vulnerability to bring your server down while it's doing important work! Of course, a proactive programmer could also take this knowledge and patch up the problem to prevent future crashes. As is common with most fuzzers, the input was "mutated" to produce this odd, incorrect HTTP request message. Will your server see the `GONT` and recognize it as an invalid method? Will it realize `HTTP/1.111` is an invalid version number? If your program handles this badly, and ends up crashing or doing something it shouldn't, then fuzzing was a success. An attacker could take advantage of this vulnerability to bring your server down while it's doing important work. Of course, a proactive programmer could also take this knowledge and patch up the problem to prevent future crashes.

17
sfi/concepts_gurthang.md Normal file
View File

@ -0,0 +1,17 @@
# Concepts: What is gurthang?
AFL and AFL++ are excellent at what they do, but they have limitations. One such limitation is how AFL feeds input to the target program: it only works with programs that read from STDIN or from a file. In many cases, this is sufficient; lots of C programs take their input from STDIN or a file.
However, this project is about creating a HTTP server. Servers don't read input through a file or STDIN - they read from network sockets. So, the question becomes: how can we force a HTTP server to read input from STDIN, so we can fuzz it with AFL? Additionally, how can we do this without modifying your source code?
Gurthang is a C library I developed to solve this problem. It works by "overloading" the `accept` system call and running some extra code to establish an internal connection to your server. Using the special `LD_PRELOAD` environment variable, it can convince your server to use gurthang's copy of `accept`, rather than the actual system call.
## Connection Multiplexing
Once called, gurthang's version of the `accept` system call spawns a controller thread. This controller threads reads input via stdin, expecting a specific file format (dubbed the **comux** file format). These comux files are designed to specify the data to be sent to the target server across multiple connections. The controller thread parses the input file, then spawns individual threads to send "chunks" of data to the target server across specific connections.
This approach allows for multiple internal client connections to be made to your server, increasing the probability of finding multithreading-related bugs. As a bonus, it requires *zero* modification to your source code. All you have to do is prepend `LD_PRELOAD=/path/to/gurthang-preload.so` to your command-line invocation of your server, then pipe one of these comux files to your process via stdin.
## AFL++ Custom Mutator
The other half of gurthang is an AFL++ custom mutator. AFL++ does great when fuzzing many programs on its own, but for more complex file formats (such as the **comux** files being used here), a custom mutator can be implemented to ensure the file's structure doesn't get overwritten during fuzzing. Gurthang's mutator (`gurthang-mutator.so`) does just that; it maintains the structure of each comux file while also randomly modifying (fuzzing) the connection data to be sent to the target server.

29
sfi/how_to_fuzz.md Normal file
View File

@ -0,0 +1,29 @@
# How do I fuzz my server? (`fuzz-pserv.py`)
Fuzzing your server can be done using the `fuzz-pserv.py` python script, located in the CS 3214 bin folder on RLogin (`/home/courses/cs3214/bin/fuzz-pserv.py`). To get started, simply run `fuzz-pserv.py` - you'll be presented with a help menu:
![](./images/img_fuzz_pserv_screenshot1.png)
Fuzzing your server is as simple as typing `fuzz-pserv.py --src-dir <your_src_dir>`. The script will compile your code with AFL++'s compiler, perform a small test run, then launch AFL++. You'll be presented with the AFL++ status screen. You can choose to either wait until the fuzzer times out (this time varies - see below), or you can use Ctrl-C to terminate it.
To understand everything displayed on the status screen, check out [AFL++'s documentation](https://aflplus.plus/docs/status_screen/). You'll probably be most interested in the "overall results" section of the status screen, displayed in the top-right corner. This gives a report of all unique crashes and hangs, as well as how many "paths" the fuzzer has discovered. (A "path" describes a unique path of code executed by your server. A "unique" crash/hang describes a crash/hang that was found on one such path.)
## Research Participation
When the fuzzing script starts, you'll be presented with a brief menu asking about research participation. This project is part of Connor Shugg's M.S. Thesis project work. If you choose to grant consent, your source code and fuzzer results will be collected and stored in a secure location for research purposes. Before making a decision, please read the forum post and full consent form displayed on the course forum and course website.
## Fuzzing Results
Once AFL++ has terminated, (either when finding a bug or after a set timeout) the script will print a summary of the crashes/hangs that were found. By default, the output directory will be placed in your pserv's src directory (specified by `--src-dir`). However, you can use the `--out-dir` switch to specify otherwise.
![](./images/img_fuzz_pserv_screenshot2.png)
If crashes or hangs are found, the directories containing the crash-inducing input files are listed in the summary. Two shell scripts will be generated and placed within the output directory. Simply give those a run to reproduce the crashes and get debugging!
## Extra Credit
Once fuzzing has finished and the fuzzing summary has been printed, you might notice a message regarding extra credit being printed:
![](./images/img_fuzz_pserv_screenshot3.png)
Using this fuzzer allows you the chance to earn extra credit on project 4. This extra credit scores you for how well your server performed under fuzzing for certain time periods. A `.tar` file is produced after each fuzzing run and is submittable to a special `p4-ex` grading endpoint for extra credit evaluation.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 279 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 MiB

After

Width:  |  Height:  |  Size: 17 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

View File

@ -4,7 +4,7 @@ The security of computer systems is extremely important. If vulnerabilities exis
Web servers are one such type of computer system, and since most are directly connected to the internet, they're tested (and often deliberately attacked) every day by thousands of users. How can we be sure a web server can gracefully handle any sort of input? Web servers are one such type of computer system, and since most are directly connected to the internet, they're tested (and often deliberately attacked) every day by thousands of users. How can we be sure a web server can gracefully handle any sort of input?
Some may argue that it's impossible to uncover _every_ bug in a system. But, we as computer scientists and computer engineers can use some effective techniques to catch most of them. Fuzzing is one such technique. This "fuzzing interface" allows you to utilize AFL++ (an advanced fuzzer) along with a special `LD_PRELOAD` library (called "sockfuzz") to fuzz your pserv implementation. This will help you uncover any bugs in your code that cause your server to crash or hang. Some may argue that it's impossible to uncover _every_ bug in a system. But, we as computer scientists and computer engineers can use some effective techniques to catch most of them. Fuzzing is one such technique. This "fuzzing interface" allows you to utilize AFL++ (an advanced fuzzer) along with a special `LD_PRELOAD` library (called "gurthang") to fuzz your pserv implementation. This will help you uncover any bugs in your code that cause your server to crash or hang.
A quick crash-course on how to get started is below. However, many more useful details can be found throughout the documentation. A quick crash-course on how to get started is below. However, many more useful details can be found throughout the documentation.
@ -12,32 +12,31 @@ A quick crash-course on how to get started is below. However, many more useful d
### **Concepts** ### **Concepts**
- [What is fuzzing?](./sfi_concepts_fuzzing.md) - [What is fuzzing?](./concepts_fuzzing.md)
- [What is AFL++?](./sfi_concepts_afl.md) - [What is AFL++?](./concepts_afl.md)
- [What is sockfuzz?](./sfi_concepts_sockfuzz.md) - [What is gurthang?](./concepts_gurthang.md)
### **Fuzzing Interface** ### **Fuzzing Interface**
- [How do I fuzz my server?](./sfi_how_to_fuzz.md) (`fuzz-pserv.py`) - [How do I fuzz my server?](./how_to_fuzz.md) (`fuzz-pserv.py`)
- [What do I do after fuzzing?](./sfi_after_fuzzing.md) (`fuzz-utils.py`) - [What do I do after fuzzing?](./after_fuzzing.md)
## Quickstart: Fuzzing your Server ## Quickstart: Fuzzing your Server
To fuzz your server, do the following: To fuzz your server, do the following:
1. Run `fuzz-pserv.py --src-dir <path_to_your_pserv_src_dir>` 1. Run `fuzz-pserv.py --src-dir /path/to/your/pserv/src`
2. Wait for it to finish (might be a while), or hit Ctrl-C once you're satisfied. 2. Wait for it to finish (might be a while), or hit Ctrl-C once you're satisfied.
3. Look at the summary: if any crashes or hangs are found, use these files to debug. 3. Look at the summary: if any crashes or hangs are found, use these files to debug.
![](./images/gif_fuzz_demo.gif) ![](./images/gif_fuzz_run.gif)
## Quickstart: Reproducing a Crash/Hang ## Quickstart: Reproducing a Crash/Hang
To reproduce a crash found by the fuzzer, do the following: To reproduce a crash found by the fuzzer, do the following:
1. Choose one of the crash files in the output directory. 1. Choose one of the crash files in the output directory.
2. Run your server in one terminal. 2. Run the following command: `/path/to/fuzzer_output/fuzz-rerun-gdb.sh < /path/to/crash/file`
3. Open another terminal and run: `fuzz-utils.py --tool send <server_address> <server_port> < <path_to_crash_file>`
4. Investigate! Your server should crash or hang, depending on the file you chose. 4. Investigate! Your server should crash or hang, depending on the file you chose.
![](./images/gif_crash_reproduce_demo.gif) ![](./images/gif_fuzz_debug.gif)

View File

@ -1,46 +0,0 @@
# What do I do after fuzzing? (`fuzz-utils.py`)
Once you've completed a fuzzing run, you'll most likely have a few output files whose contents caused your server to crash or hang. (If the fuzzer didn't report any, congratulations! Your server must be pretty robust.) Each of these files contains the exact input that was sent to your server that caused the issue. For example, from the example shown in `how_to_fuzz.md`, we can see a few of the crash files in `./fuzz_out/fuzz0/crashes`:
![](./images/img_fuzz_utils_screenshot1.png)
We can open one of these files up. Running `cat ./fuzz_out/fuzz0/crashes/id:000000,sig:11,src:000008+000068,time:193665,op:splice,rep:4` shows us the exact message that caused the server to crash:
```
GET /Opi/login HTTP/1.1
Host: hornbe/1.1
Host: hornbeam.rloginJ21564
Accept-Encoding: identity
Content-Length:
GET /api/log 64
{"username": "user0
```
Based on the name of the file (we can see `sig:11`), your server received a SIGSEGV signal (a segmentation fault). Using `fuzz-utils.py`, we can take a closer look at the file's contents and use it to recreate the same crash.
## Post-Fuzzing Utilities
Running `fuzz-utils.py` will produce a help menu, with a few tools you can use in tandem with these crash files:
![](./images/img_fuzz_utils_screenshot2.png)
With the `--tool` switch, you can specify a tool to invoke. They're discussed below.
## Hexdump Tool
To get a better idea of what's inside each crash/hang-inducing output file, a hexdump tool is built into `fuzz-utils.py`. The contents of a file are printed, in hexadecimal, when the script is invoked like so: `fuzz-utils.py --tool hex <path_to_file>`.
For example, if we take the crash file shown above and run it through the hexdump tool, we get:
![](./images/img_fuzz_utils_screenshot3.png)
## Sending Tool
To actually reproduce a crash/hang found by AFL++, you'll want to send the exact same input to your server. To do this, launch your server in one terminal, and run this in another: `fuzz-utils.py --tool send <server_address> <server_port>`. The script will read STDIN and send it through a socket to your server. To send the contents of a file, simply use I/O redirection: `fuzz-utils.py --tool send <server_address> <server_port> < <path_to_file>`.
If we consider the same example as before, we can start our server (something like: `./server -p 13650`), then, on the same machine, run `fuzz-utils.py --tool send 127.0.0.1 13650 < ./fuzz_out/fuzz0/crashes/id:000000,sig:11,src:000008+000068,time:193665,op:splice,rep:4`. When the server tries to parse the input, the segmentation fault will occur.
![](./images/img_fuzz_utils_screenshot4.png)
With this, you'll be able to debug: launch your server in GCC in one terminal, send the crash file's contents in another, and go to town.

View File

@ -1,19 +0,0 @@
# Concepts: What is sockfuzz?
AFL and AFL++ are excellent at what they do, but they have limitations. One such limitation is how AFL feeds input to the target program: it only works with programs that read from STDIN or from a file. In many cases, this is sufficient; lots of C programs take their input from STDIN or a file.
However, this project is about creating a HTTP server. Servers don't read input through a file or STDIN - they read from network sockets. So, the question becomes: how can we force a HTTP server to read input from STDIN, so we can fuzz it with AFL? Additionally, how can we do this without modifying your source code?
Sockfuzz is a small C library I developed to solve this problem. It works by "overloading" the `accept` system call and running some extra code to establish an internal connection to your server. Using the special `LD_PRELOAD` environment variable, it can convince your server to use sockfuzz's copy of `accept`, rather than the actual system call.
![](./images/img_sockfuzz_diagram1.png)
Once called, sockfuzz only allows one thread to finish the call to `accept`. The others are forced to block on a call to `sem_wait`. The one thread that is allowed through runs code that makes a connection to the server, spawns two threads, and calls the _real_ `accept` system call, returning its value. From your point of view, your server behaves just about the same when preloaded with sockfuzz, apart from using only one its threads and setting up that internal connection.
A screenshot of sockfuzz's overloaded `accept` function shows what your server's threads will do when they call sockfuzz's version of the function:
![](./images/img_sockfuzz_code1.png)
The two threads that get spawned are designated as the "input thread" and the "output thread." The input thread's job is to read STDIN (until EOF is reached) and feed it through the open network socket to the server. Once STDIN is exhausted, it exits. The output thread's job is to receive bytes from the network socket and send them straight to STDOUT. Once the connection is closed, this thread exits. Collectively, these two threads form a system to send the contents of STDIN to your server and dump the server's response to STDOUT.
![](./images/img_sockfuzz_example1.png)

View File

@ -1,29 +0,0 @@
# How do I fuzz my server? (`fuzz-pserv.py`)
Fuzzing your server can be done using the `fuzz-pserv.py` python script, located in the CS 3214 bin folder on RLogin (`/home/courses/cs3214/bin/fuzz-pserv.py`). To get started, simply run `fuzz-pserv.py` - you'll be presented with a help menu:
![](./images/img_fuzz_pserv_screenshot1.png)
Fuzzing your server is as simple as typing `fuzz-pserv.py --src-dir <your_src_dir>`. The script will compile your code with AFL++'s compiler, perform a small test run, then launch AFL++. You'll be presented with the AFL++ status screen. You can choose to either wait until the fuzzer times out (this time varies - see below), or you can use Ctrl-C to terminate it.
To understand everything displayed on the status screen, check out [AFL++'s documentation](https://aflplus.plus/docs/status_screen/). You'll probably be most interested in the "overall results" section of the status screen, displayed in the top-right corner. This gives a report of all unique crashes and hangs, as well as how many "paths" the fuzzer has discovered. (A "path" describes a unique path of code executed by your server. A "unique" crash/hang describes a crash/hang that was found on one such path.)
## Parallel Fuzzing
By default, this script invokes AFL++ using a single core on the system. However, you can specify any number of cores (up to the maximum) to spawn _multiple_ AFL++ processes (one on each core). These processes work together to find crashes/hangs - as a whole, they can typically find more bugs faster than a single process on a single core.
You can use the `--fuzz-cores` switch to specify the number of cores you wish to use.
### Timing/Core Limits
As you might know, RLogin can get pretty cluttered as we move closer to project deadlines. Parallel fuzzing is very effective, but using too many cores on a machine can prevent others from getting work done. Because of this, limits are established to prevent any one student from fuzzing with too many cores for too long.
This limit is described in "CPU-Seconds" - a maximum number of time you can fuzz that varies with the number of cores you use. The more cores you specify with `--fuzz-cores`, the less maximum time you'll be allowed to run AFL++. Using a single core (the default), you can run AFL++ for the longest time. Using two cores, you can run AFL++ for half that time. With three cores, you can run for a third of that time. (And so on.)
### Fuzzing Results
Once AFL++ has terminated (either by timeout or by Ctrl-C), the script will print a summary of the crashes/hangs that were found. By default, the output directory will be placed in your pserv's src directory (specified by `--src-dir`). However, you can use the `--out-dir` switch to specify otherwise.
![](./images/img_fuzz_pserv_screenshot2.png)
If crashes or hangs are found, the directories containing the crash-inducing input files are listed in the summary. Time to investigate those bugs!

View File

@ -19,6 +19,8 @@
#include <time.h> #include <time.h>
#include <fcntl.h> #include <fcntl.h>
#include <linux/limits.h> #include <linux/limits.h>
#include <dirent.h>
#include <jansson.h>
#include "http.h" #include "http.h"
#include "hexdump.h" #include "hexdump.h"
@ -26,6 +28,8 @@
#include "bufio.h" #include "bufio.h"
#include "main.h" #include "main.h"
static const char * NEVER_EMBED_A_SECRET_IN_CODE = "supa secret";
// Need macros here because of the sizeof // Need macros here because of the sizeof
#define CRLF "\r\n" #define CRLF "\r\n"
#define CR "\r" #define CR "\r"
@ -80,6 +84,8 @@ http_parse_request(struct http_transaction *ta)
static bool static bool
http_process_headers(struct http_transaction *ta) http_process_headers(struct http_transaction *ta)
{ {
ta->from = -1;
ta->to = -1;
for (;;) { for (;;) {
size_t header_offset; size_t header_offset;
ssize_t len = bufio_readline(ta->client->bufio, &header_offset); ssize_t len = bufio_readline(ta->client->bufio, &header_offset);
@ -112,9 +118,64 @@ http_process_headers(struct http_transaction *ta)
ta->req_content_len = atoi(field_value); ta->req_content_len = atoi(field_value);
} }
ta->valid_token = false;
/* Handle other headers here. Both field_value and field_name /* Handle other headers here. Both field_value and field_name
* are zero-terminated strings. * are zero-terminated strings.
*/ */
// Cookie header
if (!strcasecmp(field_name, "Cookie")) {
// Gets the cookies.
char* cookie2;
char* cookie1 = strtok_r(field_value, ";", &cookie2);
cookie2++;
// Stores the 2nd cookie.
ta->extra_cookie = cookie2;
// Determines if the 1st cookie is invalid.
if ((cookie1 == NULL) || (strlen(cookie1) <= 11))
{
ta->resp_status = HTTP_PERMISSION_DENIED;
return false;
}
// If not, find token.
ta->token = cookie1 + 11; // + 11 gets rid of "auth_token=" heading.
ta->valid_token = validate_token_exp(ta, ta->token);
}
// Range header
if (!strcasecmp(field_name, "Range")){
// Gets token.
char *endp;
char *token = strtok_r(field_value, "= ", &endp);
while (token != NULL && strcasecmp(token, "bytes")) {
token = strtok_r(NULL, "= ", &endp);
}
token = strtok_r(NULL, "= ", &endp);
// Determines if the token exist.
if (token != NULL) {
if (token[0] == '-') {
ta->to = atol(token + 1);
}
else if (token[strlen(token) - 1] == '-') {
token[strlen(token) - 1] = '\0';
ta->from = atol(token);
}
else {
char *to;
char *from = strtok_r(token, "- ", &to);
ta->from = atol(from);
ta->to = atol(to);
}
}
ta->range_request = true;
}
} }
} }
@ -150,7 +211,15 @@ add_content_length(buffer_t *res, size_t len)
static void static void
start_response(struct http_transaction * ta, buffer_t *res) start_response(struct http_transaction * ta, buffer_t *res)
{ {
buffer_appends(res, "HTTP/1.0 "); switch (ta->req_version) {
case HTTP_1_0:
buffer_appends(res, "HTTP/1.0 ");
break;
case HTTP_1_1:
default:
buffer_appends(res, "HTTP/1.1 ");
break;
}
switch (ta->resp_status) { switch (ta->resp_status) {
case HTTP_OK: case HTTP_OK:
@ -274,10 +343,48 @@ guess_mime_type(char *filename)
if (!strcasecmp(suffix, ".js")) if (!strcasecmp(suffix, ".js"))
return "text/javascript"; return "text/javascript";
if (!strcasecmp(suffix, ".css"))
return "text/css";
if (!strcasecmp(suffix, ".svg"))
return "image/svg+xml";
if (!strcasecmp(suffix, ".mp4"))
return "video/mp4";
return "text/plain"; return "text/plain";
} }
/* Handle HTTP transaction for static files. */ /* Check a token is not expired. */
bool validate_token_exp(struct http_transaction *ta, char* token) {
jwt_t* cookie;
// Decode token.
int rc = jwt_decode(&cookie, ta->token, (unsigned char *)NEVER_EMBED_A_SECRET_IN_CODE, strlen(NEVER_EMBED_A_SECRET_IN_CODE));
if (rc)
return false;
// Get claim (formatted grants).
char* grants = jwt_get_grants_json(cookie, NULL);
if (grants == NULL)
return false;
ta->grants = grants;
// Get expiration time.
json_error_t error;
json_t *jgrants = json_loadb(grants, strlen(grants), 0, &error);
json_int_t exp, iat;
const char *sub;
json_unpack(jgrants, "{s:I, s:I, s:s}", "exp", &exp, "iat", &iat, "sub", &sub);
// Check expiration time.
if (time(NULL) >= exp)
return false;
return true;
}
/* Handle HTTP transaction for static files. */
static bool static bool
handle_static_asset(struct http_transaction *ta, char *basedir) handle_static_asset(struct http_transaction *ta, char *basedir)
{ {
@ -288,11 +395,36 @@ handle_static_asset(struct http_transaction *ta, char *basedir)
// which? Fix it to avoid indirect object reference (IDOR) attacks. // which? Fix it to avoid indirect object reference (IDOR) attacks.
snprintf(fname, sizeof fname, "%s%s", basedir, req_path); snprintf(fname, sizeof fname, "%s%s", basedir, req_path);
char *endptr;
char *p = calloc(strlen(fname) + 1, sizeof(char));
memcpy(p, fname, strlen(fname) + 1);
char *dir = strtok_r(p, "/", &endptr);
dir = strtok_r(NULL, "/", &endptr); // initial ".." is okay
while (dir != NULL) {
if (!strcmp(dir, "..")) {
return send_not_found(ta);
}
dir = strtok_r(NULL, "/", &endptr);
}
if (!strcmp(req_path, "/")) {
memset(fname, 0, PATH_MAX);
snprintf(fname, sizeof fname, "%s%s", server_root, "/index.html");
}
if (access(fname, R_OK)) { if (access(fname, R_OK)) {
if (errno == EACCES) if (errno == EACCES)
return send_error(ta, HTTP_PERMISSION_DENIED, "Permission denied."); return send_error(ta, HTTP_PERMISSION_DENIED, "Permission denied.");
else else {
return send_not_found(ta); if (html5_fallback) {
memset(fname, 0, PATH_MAX);
snprintf(fname, sizeof fname, "%s%s", server_root, "/index.html");
} else {
return send_not_found(ta);
}
}
} }
// Determine file size // Determine file size
@ -310,9 +442,28 @@ handle_static_asset(struct http_transaction *ta, char *basedir)
http_add_header(&ta->resp_headers, "Content-Type", "%s", guess_mime_type(fname)); http_add_header(&ta->resp_headers, "Content-Type", "%s", guess_mime_type(fname));
off_t from = 0, to = st.st_size - 1; off_t from = 0, to = st.st_size - 1;
if (ta->range_request) {
if (ta->from >= 0) {
from = ta->from;
}
if (ta->to >= 0) {
to = ta->to;
}
}
off_t content_length = to + 1 - from; off_t content_length = to + 1 - from;
add_content_length(&ta->resp_headers, content_length); add_content_length(&ta->resp_headers, content_length);
http_add_header(&ta->resp_headers, "Accept-Ranges", "bytes");
if (ta->range_request) {
ta->resp_status = HTTP_PARTIAL_CONTENT;
http_add_header(&ta->resp_headers, "Content-Range",
"bytes %ld-%ld/%ld", from, to, st.st_size);
}
bool success = send_response_header(ta); bool success = send_response_header(ta);
if (!success) if (!success)
goto out; goto out;
@ -326,10 +477,207 @@ out:
return success; return success;
} }
/* Handles api/login POST */
static bool
post_handle_login(struct http_transaction *ta)
{
http_add_header(&ta->resp_headers, "Content-Type", "application/json");
char* req_body = bufio_offset2ptr(ta->client->bufio, ta->req_body);
// Gets the username and password.
req_body[ta->req_content_len] = '\0';
json_error_t error;
json_t *jgrants = json_loadb(req_body, strlen(req_body), 0, &error);
const char *username;
const char *password;
int rc = json_unpack(jgrants, "{s:s, s:s}", "username", &username, "password", &password);
if (rc == -1)
{
return send_error(ta, HTTP_PERMISSION_DENIED, "???");
}
// Authenticates the username and password.
if (!strcmp(username, "user0") && !strcmp(password, "thepassword"))
{
int iat = time(NULL);
int exp = iat + token_expiration_time;
// Create cookie.
jwt_t* cookie;
int rc = jwt_new(&cookie);
if (rc)
return send_error(ta, HTTP_INTERNAL_ERROR, "jwt_new");
// Add grants.
rc = jwt_add_grant(cookie, "sub", username);
if (rc)
return send_error(ta, HTTP_INTERNAL_ERROR, "jwt_add_grant");
//time_t age = time(NULL);
rc = jwt_add_grant_int(cookie, "iat", iat);
if (rc)
return send_error(ta, HTTP_INTERNAL_ERROR, "jwt_add_grant");
//time_t max_age = age + 3600 * 24;
rc = jwt_add_grant_int(cookie, "exp", exp);
if (rc)
return send_error(ta, HTTP_INTERNAL_ERROR, "jwt_add_grant");
// Set algorithm.
rc = jwt_set_alg(cookie, JWT_ALG_HS256, (unsigned char *)NEVER_EMBED_A_SECRET_IN_CODE, strlen(NEVER_EMBED_A_SECRET_IN_CODE));
if (rc)
return send_error(ta, HTTP_INTERNAL_ERROR, "jwt_set_alg");
// Create token.
char* token = jwt_encode_str(cookie);
if (token == NULL)
return send_error(ta, HTTP_INTERNAL_ERROR, "jwt_encode_str");
// Add Set-Cookie header.
http_add_header(&ta->resp_headers, "Set-Cookie", "auth_token=%s; Path=%s; Max-Age=%d; HttpOnly", token, "/", token_expiration_time);
// Get claim (formatted grants).
char *grants = jwt_get_grants_json(cookie, NULL);
buffer_appends(&ta->resp_body, grants);
// Send claim.
ta->resp_status = HTTP_OK;
return send_response(ta);
}
return send_error(ta, HTTP_PERMISSION_DENIED, "Permission denied");
}
/* Handles api/login GET */
static bool get_handle_login(struct http_transaction *ta) {
http_add_header(&ta->resp_headers, "Content-Type", "application/json");
// Determines if the token is invalid.
if (!ta->valid_token) {
buffer_appends(&ta->resp_body, "{}");
ta->resp_status = HTTP_OK;
return send_response(ta);
}
// If not, send response.
buffer_appends(&ta->resp_body, ta->grants);
ta->resp_status = HTTP_OK;
return send_response(ta);
}
/* Lists the .mp4 files in the given directory in the proper format. */
static char *list_videos(DIR* dir) {
json_t *list = json_array();
struct dirent *file;
while ((file = readdir(dir)) != NULL) {
char *suffix = strrchr(file->d_name, '.');
if (suffix != NULL && !strcmp(suffix, ".mp4")) {
// stat the file
char fname[PATH_MAX];
struct stat st;
snprintf(fname, sizeof(fname), "%s/%s", server_root, file->d_name);
int rc = stat(fname, &st);
if (rc == -1)
perror("Could not list videos.");
// json components
json_t *entry = json_object();
json_t *size = json_integer(st.st_size);
json_t *name = json_string(file->d_name);
rc = json_object_set(entry, "size", size);
if (rc)
perror("JSON append failed.");
rc = json_object_set(entry, "name", name);
if (rc)
perror("JSON append failed.");
// add entry
rc = json_array_append_new(list, entry);
if (rc)
perror("JSON append failed.");
}
}
return json_dumps(list, 0);
}
/* Determines what the API request is. */
static int val_api_url(struct http_transaction *ta) {
char *req_path = bufio_offset2ptr(ta->client->bufio, ta->req_path);
if (!strcmp(req_path, "/api/login")) {
return 0;
}
if (!strcmp(req_path, "/api/video")) {
return 1;
}
if (!strcmp(req_path, "/api/logout")) {
return 2;
}
return -1;
}
/* Handles API requests like login/logout/video. */
static bool static bool
handle_api(struct http_transaction *ta) handle_api(struct http_transaction *ta)
{ {
return send_error(ta, HTTP_NOT_FOUND, "API not implemented"); int val = val_api_url(ta);
if (val == -1){
return send_not_found(ta);
}
// API login
if (val == 0) {
if (ta->req_method == HTTP_POST) {
// Handle login post
return post_handle_login(ta);
}
else if (ta->req_method == HTTP_GET){
// Handle login get
return get_handle_login(ta);
}
else{
return send_error(ta, HTTP_METHOD_NOT_ALLOWED, "Unknown method.\n");
}
}
// API video
if (val == 1) {
http_add_header(&ta->resp_headers, "Accept-Ranges", "bytes");
DIR* dir = opendir(server_root);
char *json = list_videos(dir); // handling
fprintf(stderr, "json: %s\n", json);
http_add_header(&ta->resp_headers, "Content-Type", "application/json");
// Ensures response can be sent and sends it.
char *body = buffer_ensure_capacity(&ta->resp_body, MAX_HEADER_LEN);
int len = snprintf(body, strlen(json) + 2, "%s\n", json);
int length = len > MAX_HEADER_LEN ? MAX_HEADER_LEN - 1 : len;
ta->resp_body.len += length;
ta->resp_status = HTTP_OK;
return send_response(ta);
}
// API logout
if (val == 2)
{
// Add Set-Cookie header.
http_add_header(&ta->resp_headers, "Set-Cookie", "auth_token=; Path=/; Max-Age=0; HttpOnly");
// Send message.
buffer_appends(&ta->resp_body, "{\"message\":\"logging out\"}");
ta->resp_status = HTTP_OK;
return send_response(ta);
}
return false;
} }
/* Set up an http client, associating it with a bufio buffer. */ /* Set up an http client, associating it with a bufio buffer. */
@ -339,6 +687,35 @@ http_setup_client(struct http_client *self, struct bufio *bufio)
self->bufio = bufio; self->bufio = bufio;
} }
/* Handles attempts to access private files. */
static bool
handle_private(struct http_transaction *ta)
{
// Determines if the token from the 1st cookie is valid.
if (ta->valid_token) {
ta->resp_status = HTTP_OK;
return handle_static_asset(ta, server_root);
}
// Determines if there's a 2nd cookie.
if ((ta->extra_cookie == NULL) || (strlen(ta->extra_cookie) <= 11)) {
return send_error(ta, HTTP_PERMISSION_DENIED, "Permission denied.");
}
// If so, find its token.
ta->token = ta->extra_cookie + 11; // + 11 gets rid of "auth_token=" heading.
ta->valid_token = validate_token_exp(ta, ta->token);
// valid
if (ta->valid_token) {
ta->resp_status = HTTP_OK;
return handle_static_asset(ta, server_root);
}
// invalid
return send_error(ta, HTTP_PERMISSION_DENIED, "Permission denied.");
}
/* Handle a single HTTP transaction. Returns true on success. */ /* Handle a single HTTP transaction. Returns true on success. */
bool bool
http_handle_transaction(struct http_client *self) http_handle_transaction(struct http_client *self)
@ -350,6 +727,10 @@ http_handle_transaction(struct http_client *self)
if (!http_parse_request(&ta)) if (!http_parse_request(&ta))
return false; return false;
bool is_http_1_1 = false; // false
if (ta.req_version == HTTP_1_1)
is_http_1_1 = true; // true
if (!http_process_headers(&ta)) if (!http_process_headers(&ta))
return false; return false;
@ -373,7 +754,7 @@ http_handle_transaction(struct http_client *self)
rc = handle_api(&ta); rc = handle_api(&ta);
} else } else
if (STARTS_WITH(req_path, "/private")) { if (STARTS_WITH(req_path, "/private")) {
/* not implemented */ rc = handle_private(&ta);
} else { } else {
rc = handle_static_asset(&ta, server_root); rc = handle_static_asset(&ta, server_root);
} }
@ -381,5 +762,5 @@ http_handle_transaction(struct http_client *self)
buffer_delete(&ta.resp_headers); buffer_delete(&ta.resp_headers);
buffer_delete(&ta.resp_body); buffer_delete(&ta.resp_body);
return rc; return is_http_1_1 && rc;
} }

View File

@ -40,6 +40,11 @@ struct http_transaction {
size_t req_body; // ditto size_t req_body; // ditto
int req_content_len; // content length of request body int req_content_len; // content length of request body
char* extra_cookie; // the 2nd cookie if it exists.
char *token; // authentication token
bool valid_token; // true if valid, false if not.
char* grants; // exp, iat, sub
/* response related fields */ /* response related fields */
enum http_response_status resp_status; enum http_response_status resp_status;
@ -47,6 +52,10 @@ struct http_transaction {
buffer_t resp_body; buffer_t resp_body;
struct http_client *client; struct http_client *client;
off_t from; // range from
off_t to; // range to
bool range_request;
}; };
struct http_client { struct http_client {
@ -56,5 +65,6 @@ struct http_client {
void http_setup_client(struct http_client *, struct bufio *bufio); void http_setup_client(struct http_client *, struct bufio *bufio);
bool http_handle_transaction(struct http_client *); bool http_handle_transaction(struct http_client *);
void http_add_header(buffer_t * resp, char* key, char* fmt, ...); void http_add_header(buffer_t * resp, char* key, char* fmt, ...);
bool validate_token_exp(struct http_transaction *ta, char* token);
#endif /* _HTTP_H */ #endif /* _HTTP_H */

View File

@ -2,6 +2,10 @@
* Quick demo of how to use libjwt using a HS256. * Quick demo of how to use libjwt using a HS256.
* *
* @author gback, CS 3214, Spring 2018, updated Spring 2021 * @author gback, CS 3214, Spring 2018, updated Spring 2021
* Added Jansson demo Fall'22
*
* I included the necessary free() operations here which
* are needed in a long-running server.
*/ */
#include <jwt.h> #include <jwt.h>
#include <stdlib.h> #include <stdlib.h>
@ -9,6 +13,7 @@
#include <string.h> #include <string.h>
#include <time.h> #include <time.h>
#include <errno.h> #include <errno.h>
#include <jansson.h>
static const char * NEVER_EMBED_A_SECRET_IN_CODE = "supa secret"; static const char * NEVER_EMBED_A_SECRET_IN_CODE = "supa secret";
@ -56,6 +61,7 @@ main()
if (encoded == NULL) if (encoded == NULL)
die("jwt_encode_str", ENOMEM); die("jwt_encode_str", ENOMEM);
jwt_free(mytoken);
printf("encoded as %s\nTry entering this at jwt.io\n", encoded); printf("encoded as %s\nTry entering this at jwt.io\n", encoded);
jwt_t *ymtoken; jwt_t *ymtoken;
@ -65,9 +71,32 @@ main()
if (rc) if (rc)
die("jwt_decode", rc); die("jwt_decode", rc);
free(encoded);
char *grants = jwt_get_grants_json(ymtoken, NULL); // NULL means all char *grants = jwt_get_grants_json(ymtoken, NULL); // NULL means all
if (grants == NULL) if (grants == NULL)
die("jwt_get_grants_json", ENOMEM); die("jwt_get_grants_json", ENOMEM);
jwt_free(ymtoken);
printf("redecoded: %s\n", grants); printf("redecoded: %s\n", grants);
// an example of how to use Jansson
json_error_t error;
json_t *jgrants = json_loadb(grants, strlen(grants), 0, &error);
if (jgrants == NULL)
die("json_loadb", EINVAL);
free (grants);
json_int_t exp, iat;
const char *sub;
rc = json_unpack(jgrants, "{s:I, s:I, s:s}",
"exp", &exp, "iat", &iat, "sub", &sub);
if (rc == -1)
die("json_unpack", EINVAL);
printf ("exp: %lld\n", exp);
printf ("iat: %lld\n", iat);
printf ("sub: %s\n", sub);
json_decref(jgrants);
} }

View File

@ -9,6 +9,7 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <signal.h> #include <signal.h>
#include <pthread.h>
#include "buffer.h" #include "buffer.h"
#include "hexdump.h" #include "hexdump.h"
#include "http.h" #include "http.h"
@ -34,6 +35,19 @@ int token_expiration_time = 24 * 60 * 60;
// root from which static files are served // root from which static files are served
char * server_root; char * server_root;
static void *thr_func(void *c) {
struct http_client *client = c;
while (http_handle_transaction(client)){};
bufio_close(client->bufio);
return NULL;
}
/* /*
* A non-concurrent, iterative server that serves one client at a time. * A non-concurrent, iterative server that serves one client at a time.
* For each client, it handles exactly 1 HTTP transaction. * For each client, it handles exactly 1 HTTP transaction.
@ -41,18 +55,35 @@ char * server_root;
static void static void
server_loop(char *port_string) server_loop(char *port_string)
{ {
int max_clients = 8;
int num_clients = 0;
pthread_t *threads = malloc(max_clients * sizeof(pthread_t));
int accepting_socket = socket_open_bind_listen(port_string, 10000); int accepting_socket = socket_open_bind_listen(port_string, 10000);
while (accepting_socket != -1) { while (accepting_socket != -1) {
fprintf(stderr, "Waiting for client...\n"); fprintf(stderr, "Waiting for client...\n");
int client_socket = socket_accept_client(accepting_socket); int client_socket = socket_accept_client(accepting_socket);
if (client_socket == -1)
return;
struct http_client client; if (client_socket == -1){
http_setup_client(&client, bufio_create(client_socket)); return;
http_handle_transaction(&client); }
bufio_close(client.bufio); struct http_client *client = malloc(sizeof(struct http_client));
http_setup_client(client, bufio_create(client_socket));
int rc = pthread_create(&threads[num_clients++], NULL, thr_func, client);
if (rc) {
perror(NULL);
} else if (num_clients >= max_clients) {
max_clients *= 2;
threads = realloc(threads, max_clients * sizeof(pthread_t));
}
} }
for (int i = 0; i < num_clients; i++) {
pthread_join(threads[i], NULL);
}
} }
static void static void
@ -62,6 +93,7 @@ usage(char * av0)
" -p port port number to bind to\n" " -p port port number to bind to\n"
" -R rootdir root directory from which to serve files\n" " -R rootdir root directory from which to serve files\n"
" -e seconds expiration time for tokens in seconds\n" " -e seconds expiration time for tokens in seconds\n"
" -a enable HTML5 fallback\n"
" -h display this help\n" " -h display this help\n"
, av0); , av0);
exit(EXIT_FAILURE); exit(EXIT_FAILURE);

1
src/partner.json Normal file
View File

@ -0,0 +1 @@
["seofelicia", "micahmoore"]

View File

@ -54,6 +54,57 @@ socket_open_bind_listen(char * port_number_string, int backlog)
} }
char printed_addr[1024]; char printed_addr[1024];
for (pinfo = info; pinfo; pinfo = pinfo->ai_next) {
assert (pinfo->ai_protocol == IPPROTO_TCP);
int rc = getnameinfo(pinfo->ai_addr, pinfo->ai_addrlen,
printed_addr, sizeof printed_addr, NULL, 0,
NI_NUMERICHOST);
if (rc != 0) {
fprintf(stderr, "getnameinfo error: %s\n", gai_strerror(rc));
return -1;
}
/* Uncomment this to see the address returned
printf("%s: %s\n", pinfo->ai_family == AF_INET ? "AF_INET" :
pinfo->ai_family == AF_INET6 ? "AF_INET6" : "?",
printed_addr);
*/
/* Skip any non-IPv4 addresses.
* Adding support for protocol independence/IPv6 is part of the project.
*/
if (pinfo->ai_family != AF_INET6)
continue;
int s = socket(pinfo->ai_family, pinfo->ai_socktype, pinfo->ai_protocol);
if (s == -1) {
perror("socket");
return -1;
}
// See https://stackoverflow.com/a/3233022 for a good explanation of what this does
int opt = 1;
setsockopt (s, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof (opt));
rc = bind(s, pinfo->ai_addr, pinfo->ai_addrlen);
if (rc == -1) {
perror("bind");
close(s);
return -1;
}
rc = listen(s, backlog);
if (rc == -1) {
perror("listen");
close(s);
return -1;
}
freeaddrinfo(info);
return s;
}
for (pinfo = info; pinfo; pinfo = pinfo->ai_next) { for (pinfo = info; pinfo; pinfo = pinfo->ai_next) {
assert (pinfo->ai_protocol == IPPROTO_TCP); assert (pinfo->ai_protocol == IPPROTO_TCP);
int rc = getnameinfo(pinfo->ai_addr, pinfo->ai_addrlen, int rc = getnameinfo(pinfo->ai_addr, pinfo->ai_addrlen,

View File

@ -1,11 +1,13 @@
# change this as per instruction to avoid conflicts. # change this number as per instruction to avoid conflicts.
PORT=10000 PORT=10000
# to test against a working implementation (and see the intended responses) # to test against a working implementation (and see the intended responses)
# change this URL=http://theta.cs.vt.edu:3000/ # change this variable, e.g.
#URL=http://hazelnut.rlogin:12345
URL=http://localhost:${PORT} URL=http://localhost:${PORT}
# the file in which curl stores cookies across runs
COOKIEJAR=cookies.txt COOKIEJAR=cookies.txt
@ -13,24 +15,36 @@ COOKIEJAR=cookies.txt
/bin/rm ${COOKIEJAR} /bin/rm ${COOKIEJAR}
# test authentication # test authentication
# this should result in a cookie being issued that embeds the JWT token
curl -v -H "Content-Type: application/json" \ curl -v -H "Content-Type: application/json" \
-c ${COOKIEJAR} \ -c ${COOKIEJAR} \
-X POST \ -X POST \
-d '{"username":"user0","password":"thepassword"}' \ -d '{"username":"user0","password":"thepassword"}' \
${URL}/api/login ${URL}/api/login
# this should succeed if the password is correct # this should succeed if the password was correct
# curl presents the cookie from the previous request
curl -v \ curl -v \
-b ${COOKIEJAR} \ -b ${COOKIEJAR} \
${URL}/api/login ${URL}/api/login
# create a 'private' folder first. # create a 'private' folder first for your server, and
# this should fail since credentials were not presented # put a file `secret.txt` in it.
# this should fail since credentials were not presented in the request
curl -v \ curl -v \
${URL}/private/secret.txt ${URL}/private/secret.txt
# this should succeed since credentials were presented # this should succeed since credentials are included (via the cookie jar)
curl -v \ curl -v \
-b ${COOKIEJAR} \ -b ${COOKIEJAR} \
${URL}/private/secret.txt ${URL}/private/secret.txt
# now log out
curl -v -X POST \
-c ${COOKIEJAR} \
${URL}/api/logout
# this should fail since the cookie should have been removed from the cookie jar
curl -v \
-b ${COOKIEJAR} \
${URL}/private/secret.txt

File diff suppressed because it is too large Load Diff

BIN
tests/test_root_data/v1.mp4 Executable file

Binary file not shown.

BIN
tests/test_root_data/v2.mp4 Executable file

Binary file not shown.