Fix CSRF issues

GET routes are not protected against CSRF. This commit changes the needed URLs to
POST and replace simple links with forms.

Thanks @fdb-hiroshima for noticing it!
This commit is contained in:
Bat 2018-09-19 18:13:07 +01:00
parent eb24ba1774
commit d8ca1d70b7
12 changed files with 76 additions and 32 deletions

View File

@ -325,7 +325,8 @@ msgstr[1] "{{ count }} autoras en este blog: "
msgid "Login or use your Fediverse account to interact with this article" msgid "Login or use your Fediverse account to interact with this article"
msgstr "" msgstr ""
"Conéctese ou utilice a súa conta no fediverso para interactuar con este artigo" "Conéctese ou utilice a súa conta no fediverso para interactuar con este "
"artigo"
msgid "Optional" msgid "Optional"
msgstr "Opcional" msgstr "Opcional"
@ -486,7 +487,6 @@ msgstr "Descrición"
msgid "Content warning" msgid "Content warning"
msgstr "Aviso sobre o contido" msgstr "Aviso sobre o contido"
msgid "File" msgid "File"
msgstr "Ficheiro" msgstr "Ficheiro"
@ -496,7 +496,8 @@ msgstr "Enviar"
msgid "" msgid ""
"Sorry, but registrations are closed on this instance. Try to find another one" "Sorry, but registrations are closed on this instance. Try to find another one"
msgstr "" msgstr ""
"Lamentámolo, pero o rexistro está pechado en esta instancia. Intente atopar outra" "Lamentámolo, pero o rexistro está pechado en esta instancia. Intente atopar "
"outra"
msgid "Subtitle" msgid "Subtitle"
msgstr "Subtítulo" msgstr "Subtítulo"
@ -553,9 +554,10 @@ msgid ""
"Something is wrong with your CSRF token. Make sure cookies are enabled in " "Something is wrong with your CSRF token. Make sure cookies are enabled in "
"you browser, and try reloading this page. If you continue to see this error " "you browser, and try reloading this page. If you continue to see this error "
"message, please report it." "message, please report it."
msgstr "Hai un problema co seu testemuño CSRF. Asegúrese de ter as cookies activadas " msgstr ""
"no navegador, e recargue a páxina. Si persiste o aviso de este fallo, " "Hai un problema co seu testemuño CSRF. Asegúrese de ter as cookies activadas "
" informe por favor." "no navegador, e recargue a páxina. Si persiste o aviso de este fallo, "
"informe por favor."
msgid "Administration of {{ instance.name }}" msgid "Administration of {{ instance.name }}"
msgstr "Administración de {{ instance_name }}" msgstr "Administración de {{ instance_name }}"
@ -600,7 +602,12 @@ msgid "Delete your account"
msgstr "Eliminar a súa conta" msgstr "Eliminar a súa conta"
msgid "Sorry, but as an admin, you can't leave your instance." msgid "Sorry, but as an admin, you can't leave your instance."
msgstr "Lamentámolo, pero como administradora, non pode deixar a súa instancia." msgstr ""
"Lamentámolo, pero como administradora, non pode deixar a súa instancia."
msgid "Users" msgid "Users"
msgstr "Usuarias" msgstr "Usuarias"
#, fuzzy
msgid "This post isn't published yet."
msgstr "Esto é un borrador, non publicar por agora."

View File

@ -156,7 +156,7 @@ fn admin_instances_paginated(admin: Admin, conn: DbConn, page: Page) -> Template
})) }))
} }
#[get("/admin/instances/<id>/block")] #[post("/admin/instances/<id>/block")]
fn toggle_block(_admin: Admin, conn: DbConn, id: i32) -> Redirect { fn toggle_block(_admin: Admin, conn: DbConn, id: i32) -> Redirect {
if let Some(inst) = Instance::get(&*conn, id) { if let Some(inst) = Instance::get(&*conn, id) {
inst.toggle_block(&*conn); inst.toggle_block(&*conn);
@ -183,7 +183,7 @@ fn admin_users_paginated(admin: Admin, conn: DbConn, page: Page) -> Template {
})) }))
} }
#[get("/admin/users/<id>/ban")] #[post("/admin/users/<id>/ban")]
fn ban(_admin: Admin, conn: DbConn, id: i32) -> Redirect { fn ban(_admin: Admin, conn: DbConn, id: i32) -> Redirect {
User::get(&*conn, id).map(|u| u.delete(&*conn)); User::get(&*conn, id).map(|u| u.delete(&*conn));
Redirect::to(uri!(admin_users)) Redirect::to(uri!(admin_users))

View File

@ -97,14 +97,14 @@ fn details(id: i32, user: User, conn: DbConn) -> Template {
})) }))
} }
#[get("/medias/<id>/delete")] #[post("/medias/<id>/delete")]
fn delete(id: i32, _user: User, conn: DbConn) -> Redirect { fn delete(id: i32, _user: User, conn: DbConn) -> Redirect {
let media = Media::get(&*conn, id).expect("Media to delete not found"); let media = Media::get(&*conn, id).expect("Media to delete not found");
media.delete(&*conn); media.delete(&*conn);
Redirect::to(uri!(list)) Redirect::to(uri!(list))
} }
#[get("/medias/<id>/avatar")] #[post("/medias/<id>/avatar")]
fn set_avatar(id: i32, user: User, conn: DbConn) -> Redirect { fn set_avatar(id: i32, user: User, conn: DbConn) -> Redirect {
let media = Media::get(&*conn, id).expect("Media to delete not found"); let media = Media::get(&*conn, id).expect("Media to delete not found");
user.set_avatar(&*conn, media.id); user.set_avatar(&*conn, media.id);

View File

@ -338,7 +338,7 @@ fn create(blog_name: String, data: LenientForm<NewPostForm>, user: User, conn: D
} }
} }
#[get("/~/<blog_name>/<slug>/delete")] #[post("/~/<blog_name>/<slug>/delete")]
fn delete(blog_name: String, slug: String, conn: DbConn, user: User, worker: State<Pool<ThunkWorker<()>>>) -> Redirect { fn delete(blog_name: String, slug: String, conn: DbConn, user: User, worker: State<Pool<ThunkWorker<()>>>) -> Redirect {
let post = Blog::find_by_fqn(&*conn, blog_name.clone()) let post = Blog::find_by_fqn(&*conn, blog_name.clone())
.and_then(|blog| Post::find_by_slug(&*conn, slug.clone(), blog.id)); .and_then(|blog| Post::find_by_slug(&*conn, slug.clone(), blog.id));

View File

@ -118,7 +118,7 @@ fn dashboard_auth() -> Flash<Redirect> {
) )
} }
#[get("/@/<name>/follow")] #[post("/@/<name>/follow")]
fn follow(name: String, conn: DbConn, user: User, worker: Worker) -> Redirect { fn follow(name: String, conn: DbConn, user: User, worker: Worker) -> Redirect {
let target = User::find_by_fqn(&*conn, name.clone()).unwrap(); let target = User::find_by_fqn(&*conn, name.clone()).unwrap();
if let Some(follow) = follows::Follow::find(&*conn, user.id, target.id) { if let Some(follow) = follows::Follow::find(&*conn, user.id, target.id) {
@ -138,7 +138,7 @@ fn follow(name: String, conn: DbConn, user: User, worker: Worker) -> Redirect {
Redirect::to(uri!(details: name = name)) Redirect::to(uri!(details: name = name))
} }
#[get("/@/<name>/follow", rank = 2)] #[post("/@/<name>/follow", rank = 2)]
fn follow_auth(name: String) -> Flash<Redirect> { fn follow_auth(name: String) -> Flash<Redirect> {
utils::requires_login( utils::requires_login(
"You need to be logged in order to follow someone", "You need to be logged in order to follow someone",
@ -225,7 +225,7 @@ fn update(_name: String, conn: DbConn, user: User, data: LenientForm<UpdateUserF
Redirect::to(uri!(me)) Redirect::to(uri!(me))
} }
#[get("/@/<name>/delete")] #[post("/@/<name>/delete")]
fn delete(name: String, conn: DbConn, user: User, mut cookies: Cookies) -> Redirect { fn delete(name: String, conn: DbConn, user: User, mut cookies: Cookies) -> Redirect {
let account = User::find_by_fqn(&*conn, name.clone()).unwrap(); let account = User::find_by_fqn(&*conn, name.clone()).unwrap();
if user.id == account.id { if user.id == account.id {

View File

@ -472,6 +472,33 @@ main .article-meta .tags li a {
width: initial; width: initial;
} }
/** Inline forms (containing only CSRF token and a <submit>, for protected links) **/
form.inline {
display: inline;
margin: 0px;
padding: 0px;
width: auto;
}
form.inline input[type="submit"] {
display: inline-block;
color: #7765E3;
cursor: pointer;
font-size: 1em;
width: auto;
}
form.inline input[type="submit"]:not(.button) {
margin: 0;
border: none;
}
form.inline input[type="submit"]:not(.button) {
background: transparent;
color: #7765E3;
}
/* Button & Submit */ /* Button & Submit */
.button, input[type="submit"], button { .button, input[type="submit"], button {

View File

@ -18,13 +18,13 @@
<small>{{ instance.public_domain }}</small> <small>{{ instance.public_domain }}</small>
</p> </p>
{% if not instance.local %} {% if not instance.local %}
<a href="/admin/instances/{{ instance.id }}/block"> <form class="inline" method="post" action="/admin/instances/{{ instance.id }}/block">
{% if instance.blocked %} {% if instance.blocked %}
{{ "Unblock" | _ }} <input type="submit" value="{{ 'Unblock' | _ }}">
{% else %} {% else %}
{{ "Block" | _ }} <input type="submit" value="{{ 'Block' | _ }}">
{% endif %} {% endif %}
</a> </form>
{% endif %} {% endif %}
</div> </div>
{% endfor %} {% endfor %}

View File

@ -19,9 +19,9 @@
<small>@{{ user.username }}</small> <small>@{{ user.username }}</small>
</p> </p>
{% if not user.is_admin %} {% if not user.is_admin %}
<a href="/admin/users/{{ user.id }}/ban"> <form class="inline" method="post" href="/admin/users/{{ user.id }}/ban">
{{ "Ban" | _ }} <input type="submit" value="{{ 'Ban' | _ }}">
</a> </form>
{% endif %} {% endif %}
</div> </div>
{% endfor %} {% endfor %}

View File

@ -24,8 +24,12 @@
<code>{{ media.md }}</code> <code>{{ media.md }}</code>
</div> </div>
<div> <div>
<a href="/medias/{{ media.id }}/avatar" class="button inline-block">{{ "Use as avatar" | _ }}</a> <form class="inline" method="post" action="/medias/{{ media.id }}/avatar">
<a href="/medias/{{ media.id }}/delete" class="button inline-block">{{ "Delete" | _ }}</a> <input class="button" type="submit" value="{{ 'Use as avatar' | _ }}">
</form>
<form class="inline" method="post" action="/medias/{{ media.id }}/delete">
<input class="button" type="submit" value="{{ 'Delete' | _ }}">
</form>
</div> </div>
</section> </section>
{% endblock content %} {% endblock content %}

View File

@ -12,7 +12,7 @@
{% block content %} {% block content %}
<h1 class="article">{{ article.post.title }}</h1> <h1 class="article">{{ article.post.title }}</h1>
<h2 class="article">{{ article.post.subtitle }}</h2> <h2 class="article">{{ article.post.subtitle }}</h2>
<p class="article-info"> <div class="article-info">
<span class="author">{{ "Written by {{ link_1 }}{{ url }}{{ link_2 }}{{ name | escape }}{{ link_3 }}" | _( <span class="author">{{ "Written by {{ link_1 }}{{ url }}{{ link_2 }}{{ name | escape }}{{ link_3 }}" | _(
link_1='<a href="/@/', link_1='<a href="/@/',
url=author.fqn, url=author.fqn,
@ -20,19 +20,21 @@
name=author.name, name=author.name,
link_3="</a>" link_3="</a>"
) )
}}</a></span> }}</span>
&mdash; &mdash;
<span class="date">{{ date | date(format="%B %e, %Y") }}</span> <span class="date">{{ date | date(format="%B %e, %Y") }}</span>
{% if is_author %} {% if is_author %}
&mdash; &mdash;
<a href="{{ article.url}}edit">{{ "Edit" | _ }}</a> <a href="{{ article.url}}edit">{{ "Edit" | _ }}</a>
&mdash; &mdash;
<a href="{{ article.url}}delete" onclick="return confirm('Are you sure you?')">{{ "Delete this article" | _ }}</a> <form class="inline" method="post" action="{{ article.url}}delete">
<input onclick="return confirm('Are you sure you?')" type="submit" value="{{ 'Delete this article' | _ }}">
</form>
{% endif %} {% endif %}
{% if not article.post.published %} {% if not article.post.published %}
<span class="badge">{{ "Draft" }}</span> <span class="badge">{{ "Draft" }}</span>
{% endif %} {% endif %}
</p> </div>
<article> <article>
{{ article.post.content | safe }} {{ article.post.content | safe }}
</article> </article>

View File

@ -25,7 +25,9 @@
<h2>{{ "Danger zone" | _ }}</h2> <h2>{{ "Danger zone" | _ }}</h2>
<p>{{ "Be very careful, any action taken here can't be cancelled." | _ }} <p>{{ "Be very careful, any action taken here can't be cancelled." | _ }}
{% if not account.is_admin %} {% if not account.is_admin %}
<p><a class="inline-block button destructive" href="/@/{{ account.fqn }}/delete">{{ "Delete your account" | _ }}</a></p> <form method="post" action="/@/{{ account.fqn }}/delete">
<input type="submit" class="inline-block button destructive" value="{{ 'Delete your account' | _ }}">
</form>
{% else %} {% else %}
<p>{{ "Sorry, but as an admin, you can't leave your instance." | _ }}</p> <p>{{ "Sorry, but as an admin, you can't leave your instance." | _ }}</p>
{% endif %} {% endif %}

View File

@ -23,16 +23,18 @@
</div> </div>
{% if is_remote %} {% if is_remote %}
<a class="inline-block button" href="{{ user.ap_url }}" target="_blank">{{ "Open on {{ instance_url }}" | _(instance_url=instance_url) }}</a> <a class="inline-block" href="{{ user.ap_url }}" target="_blank">{{ "Open on {{ instance_url }}" | _(instance_url=instance_url) }}</a>
{% endif %} {% endif %}
{% set not_self = not is_self %} {% set not_self = not is_self %}
{% if not_self and (account is defined) %} {% if not_self and (account is defined) %}
<form class="inline" method="post" action="/@/{{ user.fqn }}/follow/">
{% if follows %} {% if follows %}
<a href="/@/{{ user.fqn }}/follow/" class="inline-block button">{{ "Unfollow" | _ }}</a> <input type="submit" value="{{ 'Unfollow' | _ }}">
{% else %} {% else %}
<a href="/@/{{ user.fqn }}/follow/" class="inline-block button">{{ "Follow" | _ }}</a> <input type="submit" value="{{ 'Follow' | _ }}">
{% endif %} {% endif %}
</form>
{% endif %} {% endif %}
</div> </div>