Revers...Filler(3pm)

πŸ“¦
version 0.5 Β· 2 versions
  • 0.5 Β· debian bookworm, debian trixie, ubuntu noble, ubuntu resolute, ubuntu stonking (latest)
  • 0.5 Β· ubuntu jammy
, in debian bookworm, debian trixie, ubuntu noble, ubuntu resolute, ubuntu stonking

✨ NAME ✨

So this is ReverseProxy::FormFiller(3pm) β€” straight out of the User Contributed Perl Documentation pile β€” and bestie, it's literally called ReverseProxy::FormFiller. And what does it do? It lets Apache fill and submit any html form in place of the user. That's the whole vibe. πŸ’…


🌸 VERSION 🌸

Version 0.5 β€” yes, she's an icon, she's serving, no cap.


πŸ’– SYNOPSIS πŸ’–

ReverseProxy::FormFiller makes an Apache server β€” whether it's sitting there as a frontal server or doing the reverse-proxy thing β€” fill in and submit html forms for your users. Like, on their behalf. They don't even have to know. 🫢

It's especially made for authentication forms. You know the situation: you want users authenticated with some account, but you do not want them to know the password or have to type it. That's the moment this module eats. But also? It works with literally any html POST form. Any of them. Obsessed.

Under the hood, ReverseProxy::FormFiller is built on Apache2 mod_perl filters. So yeah β€” you have to enable mod_perl. Non-negotiable, babe.

πŸŽ€ Basic Example

Say you want every user hitting some web app to be authenticated as "jdoe", but you absolutely refuse to publish jdoe's password (correct behaviour, honestly). If the app's authentication form lives at http://auth.example.com/login.php and looks like this:

<form id="authForm" method="POST" action="/login/">
  <div>login: <input type="text" name="login"></div>
  <div>password: <input type="password" name="password"></div>
  <div><input type="submit" value="Log in"></div>
</form>

then create an Apache virtualhost called myauth.example.com that looks like:

<VirtualHost *>
  ServerName myauth.example.com

  PerlModule ReverseProxy::FormFiller
  PerlSetVar FormFillerParamFile "/etc/apache2/FormFiller/example"

  ProxyPass        / http://auth.example.com/
  ProxyPassReverse / http://auth.example.com/

  <Location /login.php>
    RequestHeader unset Accept-Encoding
    Header        unset Content-Length
    PerlOutputFilterHandler ReverseProxy::FormFiller::output
  </Location>

  <Location /login/>
    PerlInputFilterHandler  ReverseProxy::FormFiller::input
  </Location>
</VirtualHost>

and then create a ReverseProxy::FormFiller config file at /etc/apache2/FormFiller/example, looking like:

form   => '"#authForm"',
submit => "true",
publicFormData => {
  login    => '"jdoe"',
  password => '"fake"',
},
secretFormData => {
  password => '"secret"',
},

Now here's the tea: those quotes around strings are necessary for some parameters, because some of them get interpreted as perl expressions. Don't skip them. Go look at the ReverseProxy::FormFiller config parameters for the full rundown. πŸ’œ

πŸ¦‹ Elaborate example

Okay so now imagine you want some people authenticated as "user" and some other people as "admin". Very main character of you.

Also, say just submitting the form doesn't work β€” you have to actually click the button, because clicking it runs a javascript function. Annoying but fine.

And finally, say jQuery is not loaded by the web page that displays the form. Rude of that page, honestly.

/etc/apache2/FormFiller/example will look like:

jQueryUrl => 'http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js',
form   => '"#authForm"',
submit => '"button[type=submit]"',
publicFormData => {
  login    => '$ENV{REMOTE_USER} =~ /(rtyler|msmith)/ ? "admin" : "user"',
  password => '"fake"',
},
secretFormData => {
  password => '$ENV{REMOTE_USER} =~ /(rtyler|msmith)/ ? "admin-secret" : "user-secret"',
},

🌈 Screwy example

Now for the chaotic one. Imagine you've got two authentication forms on the same page β€” one for the morning, one for the afternoon. Sure! Why not!

/etc/apache2/FormFiller/example will look like:

form   => '(localtime)[2] >= 12 ? "#morningForm" : "#afternoonForm"',
submit => "false",
publicFormData => {
  login    => '"jdoe"', # so, user believe he'll be authenticated as "jdoe"
  password => '"fake"',
},
secretFormData => {
   # but actually, he'll be authenticated as "admin" if he uses Firefox, as "user" else
  login    => '$ENV{HTTP_USER_AGENT} =~ /Firefox/ ? "admin" : "user"',
  password => '$ENV{HTTP_USER_AGENT} =~ /Firefox/ ? "admin-secret" : "user-secret"',
},

The submit => "false" there means it does not submit β€” because the form choice itself is doing the heavy lifting. Iconic deception, honestly.

⭐ Framework example

Some applications built on frameworks either use HTTP without HTML at all (think Flash), or they send POST data completely outside of any HTML form. Wild, I know.

This module lets you fill any HTML field from its jQuery selectors β€” that's what the publicFilledData parameter is for. 🎯

And on the other side, you can apply any substitution on POST datas, thanks to the postDataSub parameter. Fair warning though: it may require some tuning to get the right substitution PCRE. It's giving "fiddly but worth it."

Here's an example from a real-life GWT application:

jQueryUrl => '//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js',
form      => '"body"',
submit    => '"button.genericButton"',
publicFilledData => {
  'input.gwt-TextBox'         => '"jdoe"',
  'input.gwt-PasswordTextBox' => '"fake"',
},
postDataSub => [
  's/jdoe\|fake/jdoe\|secret/'
]

πŸ”₯ Details of Apache config πŸ”₯

πŸ’« Load Module

You do this by:

PerlModule ReverseProxy::FormFiller

This directive has to appear once in the Apache config. It can go in the server config or inside a <VirtualHost> container. Pick your fighter. ✨

πŸŽ€ Set config parameters

You do this by:

PerlSetVar FormFillerParamFile "/etc/apache2/FormFiller/example"

This directive can be set in the server config, or in any container directive (like a <VirtualHost> container, a <Location> container, or a <Directory> container). Here's the important bit: it is applied only to requests matching the corresponding container directive. So placement = power. πŸ’…

And you can set this directive several times, which means a single server can manage several forms β€” typically on different virtualhosts, but you can also manage several forms in the same virtualhost. Multitasking queen.

πŸ¦‹ Filter response body

When Apache has received the response from the remote server (if Apache is being used as a reverse-proxy) or from the backend server (if it's used as a frontend), it rewrites the html so as to fill the form and possibly submit it or click on a button. Slay.

Actually β€” and this is the lil secret β€” it does this not by directly overwriting the form, but by including some javascript that fills and submits the form. Sneaky, elegant, iconic. 🧚

This is done by the directive:

PerlOutputFilterHandler ReverseProxy::FormFiller::output

Besides, ReverseProxy::FormFiller::output can not (or not yet πŸ‘€) read zipped contents, so the HTTP request headers Content-encoding have to be removed. This is done by the directive:

RequestHeader unset Accept-Encoding

And ReverseProxy::FormFiller::output can not (or not yet) set the Content-Length response header to the modified response body's length. So you gotta remove the Content-Length response header to avoid some bugs:

Header unset Content-Length
⚠️ Do not skip that Header unset Content-Length line. Skipping it = bugs. And not the cute kind.

For performances, it's better to handle only html pages containing the aimed form. So you should place these directives in a container directive matching the form URL (like a <Location> directive), so as not to filter any html content. Efficiency is a love language. 🫢

🌸 Filter request body

When Apache receives a POST request from a client, it rewrites the request POST body, replacing empty or fake data with secret data. That's the switcheroo. This is done by the directive:

PerlInputFilterHandler  ReverseProxy::FormFiller::input

For performances, it's better to handle only requests to the form "action" URL. So you should place this directive in a container directive matching this URL (like a <Location> directive), so as not to filter any request. πŸ’–


✨ ReverseProxy::FormFiller config parameters ✨

πŸ“ Config file

okay bestie, so the config file? It looks like a .ini file but it is NOT. It's literally just a perl hash, no cap. So do not forget the commas, I'm begging you. πŸ’…

If you mess up the syntax, Apache error logs will hit you with:

<config file> content doesn't seem to be a valid perl hash

So yeah, keep those commas close. 🫢


πŸŽ€ Parameters

Most of the config parameters are interpreted as perl expressions, not just strings. That means they can rely on standard perl functions and request env vars (peep the examples below). These parameters are:

  • form
  • submit
  • publicFormData values
  • publicFilledData values
  • secretFormData values
  • javascript

That's why these parameters, if they are set to strings, need quotes around them. For example:

form => '#authForm',    # bad !
form => '"#authForm"',  # good !

Indeed, these parameters are evaled in a piece of code looking like

eval "\$x = $form";

Well, in some cases quotes are unnecessary, because Perl is laxist enough to work with not-quoted strings:

$x = "foo";   # this is right syntax
$x = foo;     # this is lazy syntax, but it works
$x = "39foo"; # this is right syntax
$x = 39foo;   # this does not work, an error is thrown "Bareword found where operator expected"

jQueryUrl

URL to load jQuery, since ReverseProxy::FormFiller response filter relies on jQuery (any version >= 1.0)

Optional: if empty or not defined, jQuery is supposed to be already loaded in the web page.

form

jQuery selector to the form to fill.

Optional: if empty or not defined, first form in web page will be filled. That is, default value is "form:first"

Here are few examples :

form => '"form#authForm"',

form => '"form:last"',

form => '(localtime)[2] >= 12 ? "#morningForm" : "#afternoonForm"',

form => '$ENV{REMOTE_USER} =~ /(rtyler|msmith)/ ? "#adminForm" : "#userForm"',

submit

To enable form autosubmit, or to automatically click on a button.

It may be "true" (autosubmit enabled), "false" (autosubmit disabled), or a jQuery selector to the button to click on (this is sometimes useful, when clicking runs a javasript function).

Optional: if empty or not defined, autosubmit is disabled - that is, default value is "false".

For example,

submit => 'true',

submit => '"button#login"',

publicFormData

Form fields to fill in html form : these data will be seen by user.

Additionnaly, these fields will be controled in POST request when the form will be submitted, to prevent malicious users to change any value.

For example,

publicFormData => {
  company  => '"SnakeOilsInc"',
  user     => '$ENV{REMOTE_USER} =~ /(rtyler|msmith)/ ? "admin" : "user"',
  password => '"hidden"'
},

Note that these data are filled through jQuery method .val(), so it works only with text inputs, password inputs, select tags and textarea, but not with checkboxes and radio buttons. In order to select on radio buttons or check on checkboxes, look at the javascript parameter.

publicFilledData

Input fields to fill, defined by jQuery selectors instead of their name attribute. This is useful if an input field has no name attribute.

publicFilledData => {
  'textarea.company'     => '"SnakeOilsInc"',
  'input#user'           => '$ENV{REMOTE_USER} =~ /(rtyler|msmith)/ ? "user" : $ENV{REMOTE_USER} =~ /dwho/ ? "admin" : "nobody"',
  'input[type=password]' => '"hidden"'
}

As same as publicFormData, these data will be seen by users, and it works only with text inputs, password inputs, select tags and textarea.

Unlike to publicFormData, these fields are not controled in POST request against malicious tampering of values.

Parameters publicFormData and publicFilledData can be used together.

secretFormData

Form fields to fill in request body, in addition or in overload to publicFormData. The main difference with publicFormData is that these data will not be filled in the html form, so users can't see them.

secretFormData => {
  password => '$ENV{REMOTE_USER} =~ /(rtyler|msmith)/ ? "admin-secret" : "user-secret"',
},

postDataSub

Substitutions to apply to POST datas. Substitutions are defined with PCRE and may use captures. They may rely on env vars, but not on perl functions.

Parameter postDataSub is an array ref and not a hash ref (unlike to publicFormData, publicFilledData and secretFormData). Hence substitutions are applied in the order they are defined.

Basic example:

postDataSub => [
  's/foo/bar/gi',
]

If POST data are made of colon-separated values and you want to change 5th value into "foo":

postDataSub => [
  's/^((.+?:){4}).+?:/$1:foo:/'  # if POST data are made of :-separated values and you want to change 5th value into "foo"
]

In order to rewrite POST data so as to force jdoe's password to "jdoe-secret" and rtyler's to "rtyler-passwd", whereas these passwords are disclosed - assume POST data is '[login]:[password]'

postDataSub => [
  's/^.*$/$ENV{REMOTE_USER}:$ENV{REMOTE_USER}/',
  's/jdoe:jdoe/jdoe:jdoe-secret/',
  's/rtyler:rtyler/rtyler:rtyler-passwd/'
]

javascript

Arbitrary javascript code to run after fields are filled, but before posting the form.

If you call jQuery through its shortcut '$', you have to escape it. Use single quotes and double quotes as in the example.

javascript => 'alert("Hello $ENV{REMOTE_USER}"); \$(input.mycheckbox).prop("checked", true)'

πŸ‘‘ AUTHOR

FX Deltombe, <fxdeltombe at gmail.com>

πŸ› BUGS

Please report any bugs or feature requests to "bug-reverseproxy-formfiller at rt.cpan.org", or through the web interface at `<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=ReverseProxy-FormFiller>`. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.

🫢 SUPPORT

You can find documentation for this module with the perldoc command.

perldoc ReverseProxy::FormFiller

You can also look for information at:

🌸 ACKNOWLEDGEMENTS

(nothing here bestie, it's giving empty πŸ’€)

Copyright 2013-2014 FX Deltombe.

This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Software Foundation; or the Artistic License.

See `http://dev.perl.org/licenses/` for more information.


and that's the whole vibe, bestie ✨ this page came from perl v5.34.0, dated 2022-06-17, and the footer reads Revers...Filler(3pm). go off, you got this πŸ’…πŸ’–

πŸ“¦ where this page comes from