• I have followed this great tutorial on how to write a plugin, which I have now adapted to do a similar thing to the hello world example shown.

    However, looking over the comments there is a-lot of concern about security and sanitizing the input.

    The plugin example simply has 2 form fields, each which write to the Options table in the database.

    I simply don’t know how to do this – could someone help me secure my plugin please, I really want to learn about this and have tried lots of things, but it always seems to break my plugin!

Viewing 15 replies - 1 through 15 (of 15 total)
  • I’m no help when it comes to writing plugins, but I just wanted to say how great it is that you’re willing to invest time and effort into doing it properly.

    It’s not a common trait.

    Cheers.

    not having looked at harknell’s link, I’ll say this:

    The way you secure your plugin depends on what you’re doing with it. If you’re inserting info into a database, you need to prevent mySQL injection. If you’re displaying user input in the html output, you need to prevent XSS. So after reading harknell’s link, if you still have questions, you’ll need to be more specific about what your plugin does (beyond writing to the options table) if you still need help.

    Moderator Samuel Wood (Otto)

    (@otto42)

    www.remarpro.com Admin

    If you’re not doing any *direct* database work, then the WordPress functions should (generally) sanitize your input for you. update_option and similar functions do that, in fact, so you should be safe from SQL injection attacks on that score.

    The other attack to worry about is the fact that you accept direct input via $_REQUEST and such. The nonce method linked to above will prevent that sort of attack, because the nonce will be required to make the inputs work and the nonce constantly changes.

    With both of those, you should be secure.

    Short version of implementing a nonce:
    – In any form elements, add a call to wp_nonce_field.
    – In a link that passes inputs, add a call to wp_nonce_url;
    – In your form or link receiving code, add a call to check_admin_referer before taking any actions.

    Pretty simple, actually. And it protects you against CSRF attacks.

    Thread Starter ninjaboy

    (@ninjaboy)

    Guys – thanks very much! I will have a good look over this over the next few days and do my best to implement the most appropriate security for what I’m trying to do.

    Ivovic – cheers man, the way I see it is that if you are going to do something, you may as well do it properly!

    Thread Starter ninjaboy

    (@ninjaboy)

    Oh, I thought it might be useful if I post my basic working example code. I have adapted this from the working example given on the site I followed to build it.

    This is not exactly what my plugin does, but the database functionality is the same – it just has a bit more to it (I’m still building functionality into it)!

    Hopefully this will make it a bit clearer what I am doing and make it easier to understand:

    <?php
    /*
    Plugin Name: Hello World Test
    Plugin URI: https://www.www.remarpro.com
    Version: 0.1
    License: GPL
    Description: A simple plugin build test with admin page
    Author: WordPress
    Author URI: https://www.www.remarpro.com
    */
    
    /*
    === RELEASE NOTES ===
    10.11.2007 - v0.1 - first version released
    */
    
    // FUNCTIONS
    
    function say_hello() {
    	$greeting = get_option('hello_greeting');
    	$target = get_option('hello_target');
    	print "$greeting $target";
    }
    
    function set_hello_options() {
    	add_option('hello_greeting','hello','What to say');
    	add_option('hello_target','world','To whom to say');
    }
    
    function unset_hello_options() {
    	delete_option('hello_greeting');
    	delete_option('hello_target');
    }
    
    function update_hello_options() {
    	$ok = false;
    
    	//INPUT VALIDATION REQUIRED
    	if ($_REQUEST['hello_greeting']) {
    		update_option('hello_greeting',$_REQUEST['hello_greeting']);
    		$ok = true;
    	}
    
    	if ($_REQUEST['hello_target']) {
    		update_option('hello_target',$_REQUEST['hello_target']);
    		$ok = true;
    	}
    
    	if ($ok) {
    	?>
    	<div id="message" class="update fade"><b>Options saved.</b>
    </div>
    	<?php
    	}
    	else {
    	?><div id="message" class="error fade">
    	Failed to save options - ensure you have something filled into each field please!
    
    	</div><?php
    	}
    }
    
    // INSTALL OR CLEANUP
    
    register_activation_hook(__FILE__,'set_hello_options');
    register_deactivation_hook(__FILE__,'unset_hello_options');
    
    // ADMIN MENU FORM
    
    function print_hello_form() {
    	$default_greeting = get_option('hello_greeting');
    	$default_target = get_option('hello_target');
    	?>
    
    	<form method="post">
    	<fieldset><legend>Greeting</legend>
    		<input type="text" name="hello_greeting" value="<?=$default_greeting?>">
    	</fieldset>
    	<fieldset><legend>Target</legend>
    		<input type="text" name="hello_target" value="<?=$default_target?>">
    	</fieldset>
    
    	<input type="submit" name="submit" value="Submit Changes" class="button"/>
    	</form>
    	<?php
    }
    
    // ADMIN MENU CONFIGURATION
    
    add_action('admin_menu','modify_menu');
    
    function modify_menu() {
    	add_options_page(
    						'Hello World Options',	//page title
    						'Hello World',			//sub-menu title
    						'manage_options',		//access/capability
    						__FILE__,				//file
    						'admin_hello_options'	//function
    					);
    }
    
    function admin_hello_options() {
    
    if ( !current_user_can('manage_options') )
    wp_die(__('You do no have permission to access this page.'));
    
    ?>
    <div class="wrap"><h2>Hello World Options</h2>
    
    <?php
    
    if ($_REQUEST['submit']) {
    	update_hello_options();
    }
    
    print_hello_form();
    ?>
    
    <h2>Output Preview</h2>
    
    	<b>Your site will display the following:</b>
    
    	<?PHP
    	$greeting = get_option('hello_greeting');
    	$target = get_option('hello_target');
    	print "$greeting $target";
    	?>
    
    </div>
    <?php
    }
    
    ?>

    For this type of code, my first concern would be XSS. As a basic example, try typing in stuff like this:

    <script>alert('xss');</script>

    If that produces a popup, you’ve got a problem. Malicious users could insert bad javascript into your site’s output via your plugin’s form.

    A couple ways to avoid this: Either run their input through kses to limit what tags they can use (like this forum and WP’s commenting system does) or use htmlspecialchars() on what they post before writing it to the database to turn their < into &lt; and break the HTML.

    Regarding KSES, I’m not sure whether it’s in the codex, but google it and look through wp source to see how it’s used if you want to know how to use it.

    Thread Starter ninjaboy

    (@ninjaboy)

    Thanks adamrbrown – thats a a really good starting point – at least I now know how to test input vulnerability, cheers!

    I tried inputting the code suggested into the options form of my plugin:
    <script>alert('xss');</script>

    RESULT: Nothing appeared on the frontend of the site where the plugin was called, when I looked at the sourcecode of the page I had this outputted:

    <script>alert(\'xss\');</script> – and no popup appeared, so I take it from that result that it’s ok on that front then?

    Im sorry at being such a dimwit – but how would I implement ‘kses’ – or can I just filter special characters? Is there a WordPress function I can use rather than bundling more code into my (very simple!) plugin?

    If the <script> tag successfully appeared in the HTML source, you’ve got a potential XSS problem. So no, you’re not okay on that front. The easiest way to fix this is to apply htmlspecialchars, as I mentioned before.

    If you want to allow some html and not allow other html, you’ll need to use kses, but I haven’t looked to see how wordpress implements it, so you’ll need to poke around in the code or else see if somebody else wants to respond…

    Moderator Samuel Wood (Otto)

    (@otto42)

    www.remarpro.com Admin

    Running data through kses in WordPress is easy. Just do something like this:

    $newtext = wp_filter_kses($oldtext);

    The set of tags it allows is very limited and quite safe. It’s what is used by default on comment input.

    If you trust the user putting this stuff in, but only to a limited extent, you can use wp_filter_post_kses instead, which is the normal filtering for Post content (on users that don’t have the unfiltered html rights).

    Finally, if you simply want to block all html, use wp_filter_nohtml_kses, which does just that. This is somewhat better than using PHP’s strip_tags, because it will handle HTML entities for you too.

    Thread Starter ninjaboy

    (@ninjaboy)

    Otto – fantastic! I knew there must be some WP functionality I could use – looks like wp_filter_kses is the way forward here!

    Adam – thanks for your help, I presumed that becuase the popup didn’t activate that it was ok… you learn something new every day!

    Everyone – thanks for your patience and great advice!

    Thread Starter ninjaboy

    (@ninjaboy)

    Ah, sorry guys – I have hit a problem! In Otto’s example above he gives an example of a variable, which I can understand. However, I just don’t seem to be able to work out how to implement this in my function that updates the options from the form created in the admin panel.

    The relevant chunk of code from my complete plugin example script is below – I would REALLY appreciate it if someone could show me how to implement wp_filter_kses in this – I am such a PHP newbie!!

    function update_hello_options() {
    	$ok = false;
    
    	//INPUT VALIDATION REQUIRED
    	if ($_REQUEST['hello_greeting']) {
    		update_option('hello_greeting',$_REQUEST['hello_greeting']);
    		$ok = true;
    	}
    
    	if ($_REQUEST['hello_target']) {
    		update_option('hello_target',$_REQUEST['hello_target']);
    		$ok = true;
    	}
    
    	if ($ok) {
    	?>
    	<div id="message" class="update fade"><b>Options saved.</b>
    </div>
    	<?php
    	}
    	else {
    	?><div id="message" class="error fade">
    	Failed to save options - ensure you have something filled into each field please!
    
    	</div><?php
    	}
    }
    Moderator Samuel Wood (Otto)

    (@otto42)

    www.remarpro.com Admin

    Three ways to do it:

    Method #1:

    update_option('hello_greeting', wp_filter_kses($_REQUEST['hello_greeting']));
    ...
    update_option('hello_target', wp_filter_kses($_REQUEST['hello_target']));

    Method #2:
    A somewhat better WordPress way would be to apply a generic filter to the input and then apply wp_filter_kses to the filter. Like this:

    $hello_greeting = apply_filters('hello_plugin_greeting',$_REQUEST['hello_greeting']));
    update_option('hello_greeting', $hello_greeting);
    ... later, outside all the other functions ...
    add_filter('hello_plugin_greeting','wp_filter_kses');

    The advantage here is that extra filters can be added or removed from the hello_greeting value in real time.

    Method #3:
    Finally, there is an easier and simpler alternative all around. In the specific case of “options”, WordPress has a function called “sanitize_option” that all option values must pass through. You can hook into that with a plugin and automatically run your option through kses. All you need is one line of code, outside all the functions:

    add_filter('sanitize_option_hello_greeting','wp_filter_kses');

    Do that for each option you use.

    Which one of the above three methods you use is up to you, each has advantages and disadvantages.

    Thread Starter ninjaboy

    (@ninjaboy)

    Otto – I can’t thankyou enough man, you have helped me (and many other budding plugin developers) so much.

    It is so important that this kind of info is available to the future of WordPress – we all have a responsibility to ensure that WordPress is kept as secure as possible – and that goes for plugins too of-course!

    I will implement this over the next few days (when I get a chance inbetween work!) and publish my (not very clever) plugin to the WordPress site!

    I will be using much of the same code to develop something a bit more clever as time goes on.

    Thread Starter ninjaboy

    (@ninjaboy)

    I have got it working, although I had to remove an extra bracket from Otto’s example. I went for option 2, because I thought it was the most flexible solution for future useage – my amended code is:

    $hello_greeting = apply_filters('hello_plugin_greeting',$_REQUEST['hello_greeting']);
    update_option('hello_greeting', $hello_greeting);

    As outlined by Otto, I also needed to include the filter OUTSIDE of any other function:

    add_filter('hello_plugin_greeting','wp_filter_kses');

    This is certainly working, as when I looked at the source code after trying to put the below example code into the admin panel for the plugin that writes to the database:

    <script>alert('xss');</script>

    it outputted the source code:

    alert(\'xss\');

    So it certainly stripped out all potential malicious code, tags and such – thanks Otto, your the man!

Viewing 15 replies - 1 through 15 (of 15 total)
  • The topic ‘Advise on plugin vulnerability needed please!’ is closed to new replies.