Textpattern CMS support forum
You are not logged in. Register | Login | Help
- Topics: Active | Unanswered
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
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
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
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
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
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
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
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
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
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
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
Re: etc_post: post anything from the public side
whocarez wrote #343148:
Changing
md5tosha1made 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