Go to main content

Textpattern CMS support forum

You are not logged in. Register | Login | Help

#31 2024-07-09 15:44:32

etc
Developer
Registered: 2010-11-11
Posts: 5,702
Website GitHub

Re: etc_post: post anything from the public side

Saving published articles looks a bit inconsistent: event="article" step="edit". But you also need to append at least two (hidden?) inputs to your form or the plugin: ID=## and save=something. Probably some other article fields are mandatory, I’m not sure. Try saving some article on the admin side and inspecting the request in the browsers console to see what is typically sent by txp.

Offline

#32 2026-04-08 14:35:07

whocarez
Plugin Author
From: Germany/Ukraine
Registered: 2007-10-08
Posts: 315
Website GitHub Mastodon Twitter

Re: etc_post: post anything from the public side

Hello, is this plugin still working in Textpattern 4.9.1? I updated recently an installation from 4.8.8 to 4.9.1, where a autoposting plugin was relying on etc_post.

I called in this plugin something like that.

$title = "test";	
$body	=	"This is a test.";
$excerpt =	"This is an excerpt.";
$keywords = "Test, Test2";
etc_post(
                array(
                      'user'  => 'newsrobot',
                      'event' => 'article',
                      'step' => 'save',
                ),
                '{"Title":"'. $title .'",
                      "Body":"'. $body .'",
                      "Excerpt":"'. $excerpt .'",
                      "Section":"autonews",
                      "Category1":"newsrobot",
                      "Status":"4",
                      "publish_now":"1",
                      "Keywords":"'. $keywords .'"
               }'
);

and that:

etc_post(
                array(
                      'user' => 'newsrobot',
                      'event' => 'image',
                      'step' => 'image_list'
                ),
                'import='.$working_dir.$title_dash.'.webp'
            );

No errors in log files. I tried it also on a special testing subpage inside <txp:php>...</txp:php> tags and with the etc_post function in my plugin itself, but it is running through without complainings or errors and posting nothing.

Any hint?

Online

#33 2026-04-08 16:06:10

jakob
Admin
From: Germany
Registered: 2005-01-20
Posts: 5,286
Website GitHub

Re: etc_post: post anything from the public side

Hmm, you’re using this in a plugin, right? Otherwise I would have pointed to this post.


TXP Builders – finely-crafted code, design and txp

Offline

#34 2026-04-08 16:43:29

whocarez
Plugin Author
From: Germany/Ukraine
Registered: 2007-10-08
Posts: 315
Website GitHub Mastodon Twitter

Re: etc_post: post anything from the public side

Yes, etc_post is called in a plugin, which receives the data via a curl post. So, the data is there in the right variables and passed over to the function etc_post, but nothing happens and there is no complaining or error.

Online

#35 2026-04-08 17:23:33

whocarez
Plugin Author
From: Germany/Ukraine
Registered: 2007-10-08
Posts: 315
Website GitHub Mastodon Twitter

Re: etc_post: post anything from the public side

ok, I played a little bit around.

$res from this line

$res = file_get_contents(ahu.'index.php', false, stream_context_create($opts));

contained this warning:

I’m sorry. I’m afraid I can’t do that; article save is an unsafe operation.

So, the problem is certainly related to a security issue.

Online

#36 Yesterday 04:41:48

colak
Admin
From: Cyprus
Registered: 2004-11-20
Posts: 9,404
Website GitHub Mastodon Twitter

Re: etc_post: post anything from the public side

You can try

SecFilterEngine Off
SecFilterScanPOST Off

to override unsafe operations security issues


Yiannis
——————————
NeMe | hblack.art | EMAP | A Sea change | Toolkit of Care
I do my best editing after I click on the submit button.

Offline

#37 Yesterday 14:37:26

etc
Developer
Registered: 2010-11-11
Posts: 5,702
Website GitHub

Re: etc_post: post anything from the public side

IIRC, the lifetime of txp cookies in 4.9 has been reduced to 1 month (instead of 3), to agree admin/public cookies. Have you recently logged into textpattern?

Offline

#38 Today 10:19:29

whocarez
Plugin Author
From: Germany/Ukraine
Registered: 2007-10-08
Posts: 315
Website GitHub Mastodon Twitter

Re: etc_post: post anything from the public side

colak wrote #343132:

You can try

SecFilterEngine Off...

to override unsafe operations security issues

I use nginx with php 8.2/8.4 running.

etc wrote #343138:

IIRC, the lifetime of txp cookies in 4.9 has been reduced to 1 month (instead of 3), to agree admin/public cookies. Have you recently logged into textpattern?

Yes, this botuser account posted last time on the 7th of April and so etc_post logged him in. And last_access is actually 2026-04-10 12:11:57, what means, that this user account is successfully logging in, but he can’t post.

I oversaw, that there is a new version 0.4 of etc_post, but that didn’t change the behavior.

$res = etc_get_contents(ahu.'index.php', $opts); contains still this warning of an unsafe operation.

Online

#39 Today 10:24:49

gaekwad
Server grease monkey
From: People's Republic of Cornwall
Registered: 2005-11-19
Posts: 4,838
GitHub

Re: etc_post: post anything from the public side

whocarez wrote #343145:

I use nginx with php 8.2/8.4 running.

I’m curious about your Nginx modules…can you post your load_module entries from your conf, including any that might be loaded from includes? Might be Naxsi, might be mod_security, might be something else.

Offline

#40 Today 10:51:37

whocarez
Plugin Author
From: Germany/Ukraine
Registered: 2007-10-08
Posts: 315
Website GitHub Mastodon Twitter

Re: etc_post: post anything from the public side

There is no load_module in my nginx.conf. So, etc_post in version 0.2 worked with textpattern 4.8.8. It stopped working after updating to textpattern 4.9.1. I see in textpattern history, that there was for example this change:

  • Security: Resolved admin-side XSS vulnerability. Many thanks to Jan Jeffrie Galvez Salloman, aka ‘0xj4n’.

Maybe this applies to etc_post?

Online

#41 Today 13:52:03

whocarez
Plugin Author
From: Germany/Ukraine
Registered: 2007-10-08
Posts: 315
Website GitHub Mastodon Twitter

Re: etc_post: post anything from the public side

After consulting several of these AI machines, grok gave me a solution for article,save. Changing

'_txp_token' => md5($nonce.get_pref('blog_uid')),

to

'_txp_token' => sha1($nonce.get_pref('blog_uid')),

made it possible to upload articles as before.

But image upload via image, image_list didn’t do the job. Consulting the machine again the algorithm figured out, that import should now use image_insert and it had to change something in the upload procedure. So, in the end, the machine manipulated the code and it is working now as before the update. Because of my limited knowledge, I’m of course not sure, if this is correct and safe, but it is working for me with Textpattern 4.9.1 and php 8.4.

Txp::get('\Textpattern\Tag\Registry')->register('etc_post');

function etc_post($atts, $thing = null) {
    global $txp_user;

    extract(lAtts(array(
        'user' => $txp_user,
        'event' => gps('event'),
        'step' => gps('step'),
        'post' => true,
        'markup' => strpos(ltrim($thing), '{') === 0 ? 'json' : 'ini'
    ), $atts));

    if (!php() || empty($user) && !($userinfo = is_logged_in())) return;

    !isset($userinfo['name']) or $user = $userinfo['name'];
    $postdata = array();
    $post = $post === true ? $_POST :
        (empty($post) ? array() : array_intersect_key($_POST, array_fill_keys(do_list_unique($post), null)));

    if (isset($thing)) switch ($markup) {
        case 'json': $postdata = json_decode(parse($thing), true); break;
        case 'csv': $data = str_getcsv(parse($thing)); $i = 0;
        foreach ($post as &$field)
            if (isset($data[$i])) $field = $data[$i++]; else break;
        break;
        default: $postdata = parse_ini_string(parse($thing));
    }

    if (!is_array($postdata) || !($postdata += $post)) return;

    $safe_user = doSlash($user);

    if (!($r = safe_row("nonce", 'txp_users', "name = '$safe_user'"))) 
        return isset($thing) ? parse($thing, false) : '';

    $oldnonce = $r['nonce'];
    $c_hash = md5(uniqid(mt_rand(), true));
    $nonce  = md5($user.pack('H*', $c_hash));
    $cookie = $user.','.$c_hash;

    safe_update(
        'txp_users',
        "nonce = '".doSlash($nonce)."', last_access = NOW()",
        "name = '$safe_user'"
    );

    // === UPLOAD DETECTION (file or import) ===
    $upload_file = null;
    if (isset($postdata['file']) && $postdata['file']) {
        $upload_file = $postdata['file'];
        unset($postdata['file']);
    } elseif (isset($postdata['import']) && $postdata['import']) {
        $upload_file = $postdata['import'];
        unset($postdata['import']);
    }

    if ($upload_file && $step === 'image_list') {
        $step = 'image_insert';
    }

    $fields = array(
        'event'      => $event,
        'step'       => $step,
        '_txp_token' => sha1($nonce . get_pref('blog_uid')),
    ) + $postdata;

    if ($upload_file) {
        // === IMAGE UPLOAD → multipart/form-data ===
        $opts = array(
            'method' => "POST",
            'header' => ["Cookie: txp_login=$cookie"],
            'content'=> $fields,
            'files'  => ['thefile[]' => $upload_file]
        );
    } else {
        // === ARTICLE SAVE etc. → application/x-www-form-urlencoded (CRITICAL) ===
        $opts = array(
            'method'=>"POST",
            'header'=>["Cookie: txp_login=$cookie",
                 "Content-Type: application/x-www-form-urlencoded"],
            'content' => http_build_query($fields)
        );
    }

    $res = etc_get_contents(ahu.'index.php', $opts);

    safe_update(
        'txp_users',
        "nonce = '".doSlash($oldnonce)."'",
        "name = '$safe_user'"
    );

    return $res === false ? parse($thing, false) : null;
}

function etc_get_contents($file, $opts = array())
{
    $opts += array('method' => 'POST', 'content' => '', 'header' => array());

    $files = $opts['files'] ?? array();
    unset($opts['files']);

    if (extension_loaded('curl')) {
        $ch = curl_init($file);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_POST, 1);

        $postfields = $opts['content'];

        // add file with modern CURLFile (only used for images)
        foreach ($files as $field => $path) {
            if (is_readable($path) && is_file($path)) {
                if (class_exists('CURLFile')) {
                    $postfields[$field] = new CURLFile(
                        $path,
                        mime_content_type($path) ?: 'application/octet-stream',
                        basename($path)
                    );
                } else {
                    $postfields[$field] = '@' . $path;
                }
            }
        }

        curl_setopt($ch, CURLOPT_POSTFIELDS, $postfields);
        curl_setopt($ch, CURLOPT_HTTPHEADER, (array)$opts['header']);

        $contents = curl_exec($ch);
        curl_close($ch);
    } else {
        if (!empty($files)) return false;
        if (is_array($opts['content'])) {
            $opts['content'] = http_build_query($opts['content']);
        }
        $opts['header'][] = "Content-Type: application/x-www-form-urlencoded";
        $contents = file_get_contents($file, false, stream_context_create(array('http' => $opts)));
    }

    return $contents;
}

Last edited by whocarez (Today 14:00:27)

Online

#42 Today 15:43:30

jakob
Admin
From: Germany
Registered: 2005-01-20
Posts: 5,286
Website GitHub

Re: etc_post: post anything from the public side

whocarez wrote #343148:

Changing md5 to sha1 made it possible to upload articles as before.

You solved it, but maybe the built-in form_token() function is what you need here.

Consulting the machine again the algorithm figured out, that import should now use image_insert and it had to change something in the upload procedure …

Thanks for the posting your modified code. I plan on trying to make a custom user area in the next few months.

I’d be interested to hear what Oleg says.


TXP Builders – finely-crafted code, design and txp

Offline

Board footer

Powered by FluxBB