[CodeIgniter] XSS Clean

Pagina: 1
Acties:

Onderwerpen


Acties:
  • 0 Henk 'm!

  • bindsa
  • Registratie: Juli 2009
  • Niet online
Sinds kort gebruik ik voor wat projectjes CodeIgniter als framework. Daarin zit ook een XSS filter ingebouwd, deze kan globaal aangezet worden, alleen hoe die filtert staat me niet zo aan. Vanwege de volgende dingen:

- Weggehaalde tags (<script> etc.) worden vervangen door [removed]
- Hij filter niet goed, zie volgende testcases:

Xss_clean() codeIgniter

Er blijft ranzige html over, quotes worden niet geconverteerd in sommige gevallen.

Ik heb dus de global setting GLOBAL_XSS_FILTERING op FALSE gezet, zodat er niet meer gefilterd wordt.

Vervolgens heb ik een helper gemaakt die ik autoload, met daarin de volgende functie:

PHP:
1
2
3
4
5
<?php
function entities($string) {
    return htmlentities($string, ENT_QUOTES | ENT_IGNORE, "UTF-8");
}
?>


Deze functie pas ik dan weer toe in de form_validation_rules, bijvoorbeeld als volgt:
PHP:
1
2
3
<?php
$this->form_validation->set_rules('phone-number', 'Telefoonnummer', 'max_length[10]|numeric|entities');
?>


Zo krijg ik netjes alles in entities wat ik in entities wil hebben, en als ik HTML uit form data wil doorkrijgen pas ik deze regel gewoon niet toe. De entities gaan (meestal) de DB in, en worden in de view weer netjes uitgelezen.

Volgens mij heb ik zo alles netjes afgevangen of mis ik dingen?

Één nadeel heb ik zelf al gevonden, bij bijvoorbeeld een edit form moeten de entities weer terugggeconverteerd worden.

Ik het volgende ook overwogen:

- Entities pas converteren in de view, dus de data gewoon raw de db in (natuurlijk wel met parametrized queries)

Wellicht is dit beter?

[ Voor 9% gewijzigd door bindsa op 05-08-2011 18:28 ]


Acties:
  • 0 Henk 'm!

  • RobIII
  • Registratie: December 2001
  • Niet online

RobIII

Admin Devschuur®

^ Romeinse Ⅲ ja!

(overleden)
Escapen doe je alleen bij weergave/presentatie voor 't medium waar de output plaats vindt. Je entities horen dus niet in je DB thuis. Wat als je een naam in een PDF wil zetten bijvoorbeeld? Dan krijg je Vroom &amp; Dreesman want PDF kan niets met HTML entities.

[edit]
Wat je in je edit zegt dus :)

[ Voor 39% gewijzigd door RobIII op 05-08-2011 18:31 ]

There are only two hard problems in distributed systems: 2. Exactly-once delivery 1. Guaranteed order of messages 2. Exactly-once delivery.

Je eigen tweaker.me redirect

Over mij


Acties:
  • 0 Henk 'm!

  • bindsa
  • Registratie: Juli 2009
  • Niet online
RobIII schreef op vrijdag 05 augustus 2011 @ 18:27:
[edit]
Wat je in je edit zegt dus :)
Sorry was hem nog even aan het uitbreiden ;) My bad.

Acties:
  • 0 Henk 'm!

  • Cartman!
  • Registratie: April 2000
  • Niet online
Kijk hier eens naar: http://htmlpurifier.org/ Werkt echt fantastisch :) En met RobIII, pas in je presentatielaag terug naar entities :)

Acties:
  • 0 Henk 'm!

  • bindsa
  • Registratie: Juli 2009
  • Niet online
Cartman! schreef op vrijdag 05 augustus 2011 @ 18:34:
Kijk hier eens naar: http://htmlpurifier.org/ Werkt echt fantastisch :) En met RobIII, pas in je presentatielaag terug naar entities :)
Eens, maar dan wil ik graag dat in mijn views, standaard alle variabielen door htmlentities() worden gehaald. Weet iemand hoe dat in CodeIgniter kan?

Net zoals in bijv. Smarty: http://www.smarty.net/docsv2/en/variable.default.modifiers

Acties:
  • 0 Henk 'm!

  • Matis
  • Registratie: Januari 2007
  • Laatst online: 14-09 17:58

Matis

Rubber Rocket

Dan definieer je toch een string helper-functie welke heel de zut door htmlentities heen haalt? Zoals dit voorbeeld: http://codeigniter.com/wiki/Parser_Variable_Modifiers/

If money talks then I'm a mime
If time is money then I'm out of time


Acties:
  • 0 Henk 'm!

  • bindsa
  • Registratie: Juli 2009
  • Niet online
Matis schreef op vrijdag 05 augustus 2011 @ 22:39:
Dan definieer je toch een string helper-functie welke heel de zut door htmlentities heen haalt? Zoals dit voorbeeld: http://codeigniter.com/wiki/Parser_Variable_Modifiers/
Dat zocht ik! Bedankt ;)

Acties:
  • 0 Henk 'm!

  • bindsa
  • Registratie: Juli 2009
  • Niet online
Hierbij mijn oplossing zodat de zoekende GoT-er er wellicht ook wat aan heeft. De oplossing is gebouwd rondt de volgende visie:

- Entities horen niet in de database
- Data uit de database wordt voor de veiligheid in elke view omgezet naar de bijbehorende entities
- View variabelen worden omgezet naar de bijbehorende entities
- Voor maximale veiligheid ben ik gegaan voor het opt-out scenario: entities worden standaard omgezet in de views, als er geen entities nodig zijn (bijv. in form fields) is er een functie call nodig die de entities decodeert.

Om het omzetten te automatiseren heb ik de core moeten extenden. Allereerst een custom loader:
FILE: MY_loader.php (in application/core):
PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
<?php
/**
 * Class extension that auto replaces all view vars with their htmlentities
 */
class MY_Loader extends CI_Loader {

    /**
     * @param $view
     * @param array $vars
     * @param bool $return
     * @return Page
     */
    public function view($view, $vars = array(), $return = FALSE) {
        if(is_array($vars)) {
            $vars = $this->_prep_output($vars);
        }
        return parent::view($view, $vars, $return);
    }

    /**
     * @param $array
     * @return array
     */
    private function _prep_output($array) {
        foreach($array as $key => $value) {
            if(is_array($value)) {
                $array[$key] = $this->_prep_output($value);
            }
            elseif (!is_object($value)) {
                $array[$key] = htmlentities($value, ENT_QUOTES, "UTF-8");
            }
        }
        return $array;
    }

    /**
     * @param string $params
     * @param bool $return
     * @param null $active_record
     * @return bool
     */
    function database($params = '', $return = FALSE, $active_record = NULL) {
        // Grab the super object
        $CI =& get_instance();

        // Do we even need to load the database class?
        if (class_exists('CI_DB') AND $return == FALSE AND $active_record == NULL AND isset($CI->db) AND is_object($CI->db)) {
            return FALSE;
        }

        require_once(BASEPATH.'database/DB'.EXT);

        // Load the DB class
        $db =& DB($params, $active_record);

        $my_driver = config_item('subclass_prefix').'DB_'.$db->dbdriver.'_driver';
        $my_driver_file = APPPATH.'core/'.$my_driver.EXT;

        if (file_exists($my_driver_file)) {
            require_once($my_driver_file);
            $db = new $my_driver(get_object_vars($db));
        }

        if ($return === TRUE) {
            return $db;
        }

        // Initialize the db variable.  Needed to prevent
        // reference errors with some configurations
        $CI->db = '';
        $CI->db = $db;
    }
}


In deze code gebeuren 2 dingen:
- View variabelen die geen object zijn worden omgezet naar entities
- Een custom db extension wordt geinitialiseerd i.p.v. de native db class van CodeIgniter

Wat dit bereikt:
- Alle variabelen in views die geen object zijn worden omgezet naar de bijbehorende entities.

De meest voorkomende objecten in CodeIgniter views zijn databse resultaten. Ook deze laat ik geautomatiseerd om zetten in views:

FILE: MY_DB_mysql_driver.php (in application/core):
PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?php  if ( ! defined('BASEPATH')) exit('No direct script access allowed');

class MY_DB_mysql_driver extends CI_DB_mysql_driver {

    function load_rdriver()
    {
        $driver = 'CI_DB_'.$this->dbdriver.'_result';

        if ( ! class_exists($driver))
        {
            include_once(BASEPATH.'database/DB_result'.EXT);
            include_once(BASEPATH.'database/drivers/'.$this->dbdriver.'/'.$this->dbdriver.'_result'.EXT);
        }

        $CI =& get_instance();

        $my_driver = config_item('subclass_prefix').'DB_'.$this->dbdriver.'_result';

        $my_driver_file = APPPATH.'core/'.$my_driver.EXT;

        if (file_exists($my_driver_file))
        {
            require_once($my_driver_file);
            return $my_driver;
        }
        else
        {
            return $driver;
        }
    }
}
?>

In deze code gebeurt niets anders dan een andere result klasse te definiëren dan de native klasse.

FILE: MY_DB_mysql_result (in application/core):
PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<?php  if ( ! defined('BASEPATH')) exit('No direct script access allowed');

class MY_DB_mysql_result extends CI_DB_mysql_result {

    /**
     * @param string $type
     * @return array|mixed
     */
    public function view_result($type = 'object') {
        $objects = parent::result($type);
        foreach($objects as $key => $object) {
            $vars = get_object_vars($object);
            foreach($vars as $key => $value) {
                $object->$key = entities($value);
                $objects[$key] = $object;
            }
        }
        return $objects;

    }

    /**
     * @return array|string
     */
    public function view_result_array() {
        return entities(parent::result_array());
    }

    /**
     * @param int $n
     * @param string $type
     * @return mixed
     */
    public function view_row($n = 0, $type = 'object') {
        $object =  parent::row($n, $type);
        $vars = get_object_vars($object);
        foreach($vars as $key => $value) {
            $object->$key = entities($value);
        }
        return $object;
    }

    /**
     * @param int $n
     * @return array|string
     */
    public function view_row_array($n = 0) {
        return entities(parent::row_array($n));
    }

}
?>

In deze code wordt voor elke result functie die CodeIgniter kent een daarbij horende view functie gedefinieerd die gebruikt moet worden in de view loops. De view_ functies doen exact hetzelfde als de originele resultaat functies, met het verschil dat ze entities omzetten.

Tot slot heb ik nog een extension van de security helper gemaakt die functies bevat om handmatig entities om te zetten (bijv. form input values):

FILE: MY_security_helper (in application/helpers):
PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<?php

/**
 * @param $array
 * @return array|string
 */
function entities($array) {
    if(is_array($array)) {
        array_walk_recursive($array, '_prep_entities');
    }
    else {
        $array = htmlentities($array, ENT_QUOTES, "UTF-8");
    }
    return $array;
}

/**
 * @param $array
 * @return array|string
 */
function no_entities($array) {
    if(is_array($array)) {
        array_walk_recursive($array, '_rem_entities');
    }
    else {
        $array = html_entity_decode($array, ENT_QUOTES, "UTF-8");
    }
    return $array;
}

/**
 * @param $item
 * @param $key
 * @return void
 */
function _prep_entities(&$item, $key) {
    $item = htmlentities($item, ENT_QUOTES, "UTF-8");
}

/**
 * @param $item
 * @param $key
 * @return void
 */
function _rem_entities(&$item, $key) {
    $item = html_entity_decode($item, ENT_QUOTES, "UTF-8");
}
?>

Acties:
  • 0 Henk 'm!

  • ReenL
  • Registratie: Augustus 2010
  • Laatst online: 14-09-2022
Je haalt 2 dingen door de war in dit hele verhaal.

- Wanneer je text van iemand verwacht, dan hoor je altijd htmlentities te gebruiken bij weergave, ongeacht of er html in zit of niet;
- Wanneer je iemand het recht geeft om opmaak toe te voegen en je kiest ervoor om hem/haar HTML in te laten voeren, dan zul je het zooitje moeten parsen en valideren, in dit geval kun je html-purifier gebruiken zoals eerder aangegeven.

Dus html purifier niet gebruiken voor:
- (Bedrijfs/Voor/Achter/Bij)Namen;
- Adresgegevens;
- Postcode;
- Telefoonnummer;
- Commentaar veld;
- etc.

En eventueel wel voor:
- Topic/Nieuwsposts die opmaak mogen bevatten;
- Commentaar velden die opmaak mogen bevatten.

Ps: Heeft codeigniter niet iets van view helpers en/of functies, je driver en resultset horen zich niet bewust te zijn van de werking van HTML dit hoort echt in je view thuis.

Acties:
  • 0 Henk 'm!

  • bindsa
  • Registratie: Juli 2009
  • Niet online
ReenL schreef op dinsdag 09 augustus 2011 @ 01:18:
Je haalt 2 dingen door de war in dit hele verhaal.

- Wanneer je text van iemand verwacht, dan hoor je altijd htmlentities te gebruiken bij weergave, ongeacht of er html in zit of niet;
- Wanneer je iemand het recht geeft om opmaak toe te voegen en je kiest ervoor om hem/haar HTML in te laten voeren, dan zul je het zooitje moeten parsen en valideren, in dit geval kun je html-purifier gebruiken zoals eerder aangegeven.

Dus html purifier niet gebruiken voor:
- (Bedrijfs/Voor/Achter/Bij)Namen;
- Adresgegevens;
- Postcode;
- Telefoonnummer;
- Commentaar veld;
- etc.

En eventueel wel voor:
- Topic/Nieuwsposts die opmaak mogen bevatten;
- Commentaar velden die opmaak mogen bevatten.
Je eerste punt, dat doet mijn code dus precies, bij tekstweergave altijd entities (lees: in de views), anders niet, dus bij bijv. string comparison in de controller geen entities. (uitzonderingen daargelaten, zoals genoemd data in form input values)
Je tweede punt, dat je HTML moet valideren, dat weet ik. Het gaat alleen buiten de scope van de doel van dit script (namelijk: entities vervangen in views), en is eigenlijk weer een losstaand probleem.
Ps: Heeft codeigniter niet iets van view helpers en/of functies, je driver en resultset horen zich niet bewust te zijn van de werking van HTML dit hoort echt in je view thuis.
Dat is ook exact zoals ik het doe: ik heb alleen functies aangepast (geextend) die de view laden en weergeven. Verder heb ik wat functies specifiek voor gebruik in views gemaakt (de database result functies). Lijkt me niet raar toch?

Voor de duidelijkheid nog even het doel van het script:
Een opt-out scenario creëren in views m.b.t. htmlentities(). Voor de veiligheid
Dus i.p.v.:
PHP:
1
<?=htmlentities($value)?>

Nu dit:
PHP:
1
<?=$value?>

Met hetzelfde resultaat.
En met dit als extra optie:
PHP:
1
<?=no_entities($value)?>

Voor als entities niet gewenst zijn

Bovenstaande geldt en werkt dus alleen in views.

[ Voor 10% gewijzigd door bindsa op 09-08-2011 10:18 ]

Pagina: 1